Skip to content

Commit

Permalink
Merge pull request #150 from jwlodek/add-version-pvs
Browse files Browse the repository at this point in the history
Adding records for tracking version of IOC and PandA firmware
  • Loading branch information
jwlodek authored Nov 18, 2024
2 parents a126c15 + 5c8b2d4 commit 4d6eea0
Show file tree
Hide file tree
Showing 7 changed files with 345 additions and 10 deletions.
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

0 comments on commit 4d6eea0

Please sign in to comment.