Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding records for tracking version of IOC and PandA firmware #150

Merged
merged 10 commits into from
Nov 18, 2024
1 change: 1 addition & 0 deletions src/pandablocks_ioc/_pvi.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ class PviGroup(Enum):
CAPTURE = "Capture"
HDF = "HDF"
TABLE = "Table" # TODO: May not need this anymore
VERSIONS = "Versions"


@dataclass
Expand Down
89 changes: 88 additions & 1 deletion src/pandablocks_ioc/ioc.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
Arm,
ChangeGroup,
Disarm,
Get,
GetBlockInfo,
GetChanges,
GetFieldInfo,
Expand Down Expand Up @@ -66,6 +67,7 @@
trim_description,
trim_string_value,
)
from ._version import __version__

# TODO: Try turning python.analysis.typeCheckingMode on, as it does highlight a couple
# of possible errors
Expand Down Expand Up @@ -175,6 +177,51 @@ def create_softioc(
asyncio.run_coroutine_threadsafe(client.close(), dispatcher.loop).result()


def get_panda_versions(idn_repsonse: str) -> dict[EpicsName, str]:
"""Function that parses version info from the PandA's response to the IDN command

See: https://pandablocks-server.readthedocs.io/en/latest/commands.html#system-commands

Args:
idn_response (str): Response from PandA to Get(*IDN) command

Returns:
dict[EpicsName, str]: Dictionary mapping firmware record name to version
"""

# Currently, IDN reports sw, fpga, and rootfs versions
firmware_versions = {"PandA SW": "Unknown", "FPGA": "Unknown", "rootfs": "Unknown"}

# If the *IDN response contains too many keys, break and leave versions as "Unknown"
# Since spaces are used to deliminate versions and can also be in the keys and
# values, if an additional key is present that we don't explicitly handle,
# our approach of using regex matching will not work.
if sum(name in idn_repsonse for name in firmware_versions) < idn_repsonse.count(
":"
):
logging.error(
f"Recieved unexpected version numbers in version string {idn_repsonse}!"
)
else:
for firmware_name in firmware_versions:
pattern = re.compile(
rf'{re.escape(firmware_name)}:\s*([^:]+?)(?=\s*\b(?: \
{"|".join(map(re.escape, firmware_versions))}):|$)'
)
if match := pattern.search(idn_repsonse):
firmware_versions[firmware_name] = match.group(1).strip()
logging.info(
f"{firmware_name} Version: {firmware_versions[firmware_name]}"
)
else:
logging.warning(f"Failed to get {firmware_name} version information!")

return {
EpicsName(firmware_name.upper().replace(" ", "_")): version
for firmware_name, version in firmware_versions.items()
}


async def introspect_panda(
client: AsyncioClient,
) -> tuple[dict[str, _BlockAndFieldInfo], dict[EpicsName, RecordValue]]:
Expand Down Expand Up @@ -1824,6 +1871,40 @@ def create_block_records(

return record_dict

def create_version_records(self, firmware_versions: dict[EpicsName, str]):
"""Creates handful of records for tracking versions of IOC/Firmware via EPICS

Args:
firmware_versions (dict[str, str]): Dictionary mapping firmwares to versions
"""

system_block_prefix = "SYSTEM"

ioc_version_record_name = EpicsName(system_block_prefix + ":IOC_VERSION")
ioc_version_record = builder.stringIn(
ioc_version_record_name, DESC="IOC Version", initial_value=__version__
)
add_automatic_pvi_info(
PviGroup.VERSIONS,
ioc_version_record,
ioc_version_record_name,
builder.stringIn,
)

for firmware_name, version in firmware_versions.items():
firmware_record_name = EpicsName(
system_block_prefix + f":{firmware_name}_VERSION"
)
firmware_ver_record = builder.stringIn(
firmware_record_name, DESC=firmware_name, initial_value=version
)
add_automatic_pvi_info(
PviGroup.VERSIONS,
firmware_ver_record,
firmware_record_name,
builder.stringIn,
)

def initialise(self, dispatcher: asyncio_dispatcher.AsyncioDispatcher) -> None:
"""Perform any final initialisation code to create the records. No new
records may be created after this method is called.
Expand Down Expand Up @@ -1851,15 +1932,21 @@ async def create_records(
"""Query the PandA and create the relevant records based on the information
returned"""

# Get version information from PandA using IDN command
idn_response = await client.send(Get("*IDN"))
fw_vers_dict = get_panda_versions(idn_response)

(panda_dict, all_values_dict) = await introspect_panda(client)

# Dictionary containing every record of every type
all_records: dict[EpicsName, RecordInfo] = {}

record_factory = IocRecordFactory(client, record_prefix, all_values_dict)

# For each field in each block, create block_num records of each field
# Add records for version of IOC, FPGA, and software to SYSTEM block
record_factory.create_version_records(fw_vers_dict)

# For each field in each block, create block_num records of each field
for block, panda_info in panda_dict.items():
block_info = panda_info.block_info
values = panda_info.values
Expand Down
21 changes: 21 additions & 0 deletions tests/fixtures/mocked_panda.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
ChangeGroup,
Command,
Disarm,
Get,
GetBlockInfo,
GetChanges,
GetFieldInfo,
Expand Down Expand Up @@ -430,6 +431,10 @@ def multiple_seq_responses(table_field_info, table_data_1, table_data_2):
GetChanges is polled at 10Hz if a different command isn't made.
"""
return {
command_to_key(Get(field="*IDN")): repeat(
"PandA SW: 3.0-11-g6422090 FPGA: 3.0.0C4 86e5f0a2 07d202f8 \
rootfs: PandA 3.1a1-1-g22fdd94"
),
command_to_key(
Put(
field="SEQ1.TABLE",
Expand Down Expand Up @@ -564,6 +569,10 @@ def no_numbered_suffix_to_metadata_responses(table_field_info, table_data_1):
doesn't have a suffixed number.
"""
return {
command_to_key(Get(field="*IDN")): repeat(
"PandA SW: 3.0-11-g6422090 FPGA: 3.0.0C4 86e5f0a2 07d202f8 \
rootfs: PandA 3.1a1-1-g22fdd94"
),
command_to_key(
Put(
field="SEQ.TABLE",
Expand Down Expand Up @@ -639,6 +648,10 @@ def faulty_multiple_pcap_responses():
),
}
return {
command_to_key(Get(field="*IDN")): repeat(
"PandA SW: 3.0-11-g6422090 FPGA: 3.0.0C4 86e5f0a2 07d202f8 \
rootfs: PandA 3.1a1-1-g22fdd94"
),
command_to_key(GetFieldInfo(block="PCAP1", extended_metadata=True)): repeat(
pcap_info
),
Expand Down Expand Up @@ -681,6 +694,10 @@ def standard_responses_no_panda_update(table_field_info, table_data_1):
Used to test if the softioc can be started.
"""
return {
command_to_key(Get(field="*IDN")): repeat(
"PandA SW: 3.0-11-g6422090 FPGA: 3.0.0C4 86e5f0a2 07d202f8 \
rootfs: PandA 3.1a1-1-g22fdd94"
),
command_to_key(GetFieldInfo(block="PCAP", extended_metadata=True)): repeat(
{
"TRIG_EDGE": EnumFieldInfo(
Expand Down Expand Up @@ -737,6 +754,10 @@ def standard_responses(table_field_info, table_data_1, table_data_2):
GetChanges is polled at 10Hz if a different command isn't made.
"""
return {
command_to_key(Get(field="*IDN")): repeat(
"PandA SW: 3.0-11-g6422090 FPGA: 3.0.0C4 86e5f0a2 07d202f8 \
rootfs: PandA 3.1a1-1-g22fdd94"
),
command_to_key(GetFieldInfo(block="PCAP", extended_metadata=True)): repeat(
{
"TRIG_EDGE": EnumFieldInfo(
Expand Down
128 changes: 128 additions & 0 deletions tests/test-bobfiles/SYSTEM.bob
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
<display version="2.0.0">
<name>SYSTEM</name>
<x>0</x>
<y use_class="true">0</y>
<width>506</width>
<height>166</height>
<grid_step_x>4</grid_step_x>
<grid_step_y>4</grid_step_y>
<widget type="label" version="2.0.0">
<name>Title</name>
<class>TITLE</class>
<text>SYSTEM</text>
<x use_class="true">0</x>
<y use_class="true">0</y>
<width>506</width>
<height>25</height>
<font use_class="true">
<font name="Header 1" family="Liberation Sans" style="BOLD" size="22.0">
</font>
</font>
<foreground_color use_class="true">
<color name="Text" red="0" green="0" blue="0">
</color>
</foreground_color>
<transparent use_class="true">true</transparent>
<horizontal_alignment>1</horizontal_alignment>
</widget>
<widget type="group" version="2.0.0">
<name>VERSIONS</name>
<x>5</x>
<y>30</y>
<width>496</width>
<height>131</height>
<transparent>true</transparent>
<widget type="label" version="2.0.0">
<name>Label</name>
<text>Ioc Version</text>
<x>0</x>
<y>0</y>
<width>250</width>
<height>20</height>
<tooltip>$(text)</tooltip>
</widget>
<widget type="textupdate" version="2.0.0">
<name>TextUpdate</name>
<pv_name>TEST_PREFIX:SYSTEM:IOC_VERSION</pv_name>
<x>255</x>
<y>0</y>
<width>205</width>
<height>20</height>
<font>
<font name="Default Bold" family="Liberation Sans" style="BOLD" size="14.0">
</font>
</font>
<horizontal_alignment>1</horizontal_alignment>
<format>6</format>
</widget>
<widget type="label" version="2.0.0">
<name>Label</name>
<text>Panda Sw Version</text>
<x>0</x>
<y>25</y>
<width>250</width>
<height>20</height>
<tooltip>$(text)</tooltip>
</widget>
<widget type="textupdate" version="2.0.0">
<name>TextUpdate</name>
<pv_name>TEST_PREFIX:SYSTEM:PANDA_SW_VERSION</pv_name>
<x>255</x>
<y>25</y>
<width>205</width>
<height>20</height>
<font>
<font name="Default Bold" family="Liberation Sans" style="BOLD" size="14.0">
</font>
</font>
<horizontal_alignment>1</horizontal_alignment>
<format>6</format>
</widget>
<widget type="label" version="2.0.0">
<name>Label</name>
<text>Fpga Version</text>
<x>0</x>
<y>50</y>
<width>250</width>
<height>20</height>
<tooltip>$(text)</tooltip>
</widget>
<widget type="textupdate" version="2.0.0">
<name>TextUpdate</name>
<pv_name>TEST_PREFIX:SYSTEM:FPGA_VERSION</pv_name>
<x>255</x>
<y>50</y>
<width>205</width>
<height>20</height>
<font>
<font name="Default Bold" family="Liberation Sans" style="BOLD" size="14.0">
</font>
</font>
<horizontal_alignment>1</horizontal_alignment>
<format>6</format>
</widget>
<widget type="label" version="2.0.0">
<name>Label</name>
<text>Rootfs Version</text>
<x>0</x>
<y>75</y>
<width>250</width>
<height>20</height>
<tooltip>$(text)</tooltip>
</widget>
<widget type="textupdate" version="2.0.0">
<name>TextUpdate</name>
<pv_name>TEST_PREFIX:SYSTEM:ROOTFS_VERSION</pv_name>
<x>255</x>
<y>75</y>
<width>205</width>
<height>20</height>
<font>
<font name="Default Bold" family="Liberation Sans" style="BOLD" size="14.0">
</font>
</font>
<horizontal_alignment>1</horizontal_alignment>
<format>6</format>
</widget>
</widget>
</display>
Loading