Skip to content

Commit

Permalink
Merge pull request #15 from evalott100/adjust_to_use_table_packing_in…
Browse files Browse the repository at this point in the history
…_utils

Altered code to use the new utils functions
  • Loading branch information
evalott100 authored Sep 13, 2023
2 parents 726e0ba + 0216634 commit 35360f4
Show file tree
Hide file tree
Showing 3 changed files with 23 additions and 136 deletions.
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ dependencies = [
"click",
"h5py",
"softioc>=4.4.0",
"pandablocks>=0.3.1",
"pandablocks>=0.5.1",
"pvi>=0.5",
] # Add project dependencies here, e.g. ["click", "numpy"]
dynamic = ["version"]
Expand Down
63 changes: 20 additions & 43 deletions src/pandablocks_ioc/_tables.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from pandablocks.asyncio import AsyncioClient
from pandablocks.commands import GetMultiline, Put
from pandablocks.responses import TableFieldDetails, TableFieldInfo
from pandablocks.utils import table_to_words, words_to_table
from pvi.device import ComboBox, SignalRW, TableWrite, TextWrite
from softioc import alarm, builder, fields
from softioc.imports import db_put_field
Expand Down Expand Up @@ -300,11 +301,7 @@ def __init__(
# updater are also in the same bit order.
value = all_values_dict[table_name]
assert isinstance(value, list)
field_data = TablePacking.unpack(
self.field_info.row_words,
self.table_fields_records,
value,
)
field_data = words_to_table(value, field_info)

for i, (field_name, field_record_container) in enumerate(
self.table_fields_records.items()
Expand All @@ -315,16 +312,12 @@ def __init__(
full_name = EpicsName(full_name)
description = trim_description(field_details.description, full_name)

waveform_val = self._construct_waveform_val(
field_data, field_name, field_details
)

field_record: RecordWrapper = builder.WaveformOut(
full_name,
DESC=description,
validate=self.validate_waveform,
on_update_name=self.update_waveform,
initial_value=waveform_val,
initial_value=field_data[field_name],
length=field_info.max_length,
)

Expand Down Expand Up @@ -362,7 +355,7 @@ def __init__(
# PythonSoftIOC issue #53 may alleviate this.
initial_value = (
field_data[field_name][DEFAULT_INDEX]
if field_data[field_name].size > 0
if len(field_data[field_name]) > 0
else 0
)

Expand Down Expand Up @@ -391,7 +384,7 @@ def __init__(
scalar_record = builder.mbbIn(
scalar_record_name,
*field_details.labels,
initial_value=initial_value,
initial_value=field_details.labels.index(initial_value),
DESC=scalar_record_desc,
)

Expand Down Expand Up @@ -486,21 +479,6 @@ def __init__(
SignalRW(index_record_name, index_record_name, TextWrite()),
)

def _construct_waveform_val(
self,
field_data: Dict[str, UnpackedArray],
field_name: str,
field_details: TableFieldDetails,
):
"""Convert the values into the right form. For enums this means converting
the numeric values PandA sends us into the string representation. For all other
types the numeric representation is used."""
return (
[field_details.labels[x] for x in field_data[field_name]]
if field_details.labels
else field_data[field_name]
)

def validate_waveform(self, record: RecordWrapper, new_val) -> bool:
"""Controls whether updates to the waveform records are processed, based on the
value of the MODE record.
Expand Down Expand Up @@ -566,9 +544,14 @@ async def update_mode(self, new_val: int):
try:
# Send all EPICS data to PandA
logging.info(f"Sending table data for {self.table_name} to PandA")
packed_data = TablePacking.pack(
self.field_info.row_words, self.table_fields_records
)

table = {}
for x in self.table_fields_records:
record_info = self.table_fields_records[x].record_info
if record_info:
table[x] = record_info.record.get()

packed_data = table_to_words(table, self.field_info)

panda_field_name = epics_to_panda_name(self.table_name)
await self.client.send(Put(panda_field_name, packed_data))
Expand All @@ -593,9 +576,7 @@ async def update_mode(self, new_val: int):
return

assert isinstance(old_val, list)
field_data = TablePacking.unpack(
self.field_info.row_words, self.table_fields_records, old_val
)
field_data = words_to_table(old_val, self.field_info)
for field_name, field_record in self.table_fields_records.items():
assert field_record.record_info
# Table records are never In type, so can always disable processing
Expand All @@ -615,9 +596,7 @@ async def update_mode(self, new_val: int):
panda_field_name = epics_to_panda_name(self.table_name)
panda_vals = await self.client.send(GetMultiline(f"{panda_field_name}"))

field_data = TablePacking.unpack(
self.field_info.row_words, self.table_fields_records, panda_vals
)
field_data = words_to_table(panda_vals, self.field_info)

for field_name, field_record in self.table_fields_records.items():
assert field_record.record_info
Expand All @@ -641,17 +620,15 @@ def update_table(self, new_values: List[str]) -> None:
curr_mode = TableModeEnum(self.mode_record_info.record.get())

if curr_mode == TableModeEnum.VIEW:
field_data = TablePacking.unpack(
self.field_info.row_words, self.table_fields_records, list(new_values)
)
field_data = words_to_table(new_values, self.field_info)

for field_name, field_record in self.table_fields_records.items():
assert field_record.record_info
waveform_val = self._construct_waveform_val(
field_data, field_name, field_record.field
)

# Must skip processing as the validate method would reject the update
field_record.record_info.record.set(waveform_val, process=False)
field_record.record_info.record.set(
field_data[field_name], process=False
)
self._update_scalar(field_record.record_info.record.name)

# All items in field_data have the same length, so just use 0th.
Expand Down
94 changes: 2 additions & 92 deletions tests/test_tables.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import asyncio
import typing
from typing import Dict, List, Union
from typing import Dict, List

import numpy
import numpy.testing
Expand All @@ -9,7 +9,7 @@
from fixtures.mocked_panda import TIMEOUT, command_to_key
from mock import AsyncMock, patch
from mock.mock import MagicMock, PropertyMock, call
from numpy import array, ndarray
from numpy import ndarray
from pandablocks.asyncio import AsyncioClient
from pandablocks.commands import GetMultiline, Put
from pandablocks.responses import TableFieldDetails, TableFieldInfo
Expand All @@ -18,9 +18,7 @@
from pandablocks_ioc._tables import (
TableFieldRecordContainer,
TableModeEnum,
TablePacking,
TableUpdater,
UnpackedArray,
)
from pandablocks_ioc._types import EpicsName, InErrorException, RecordInfo, RecordValue

Expand Down Expand Up @@ -347,87 +345,6 @@ async def test_create_softioc_update_table_scalars_change(
repeats_monitor.close()


def test_table_packing_unpack(
table_field_info: TableFieldInfo,
table_fields_records: Dict[str, TableFieldRecordContainer],
table_data_1: List[str],
table_unpacked_data: typing.OrderedDict[EpicsName, ndarray],
):
"""Test table unpacking works as expected"""
assert table_field_info.row_words
unpacked = TablePacking.unpack(
table_field_info.row_words, table_fields_records, table_data_1
)

actual: Union[UnpackedArray, List[str]]
for field_name, actual in unpacked.items():
expected = table_unpacked_data[EpicsName(field_name)]
if expected.dtype.char in ("S", "U"):
# Convert numeric array back to strings
actual = [table_fields_records[field_name].field.labels[x] for x in actual]
numpy.testing.assert_array_equal(actual, expected)


def test_table_packing_pack(
table_field_info: TableFieldInfo,
table_fields_records: Dict[str, TableFieldRecordContainer],
table_data_1: List[str],
):
"""Test table unpacking works as expected"""
assert table_field_info.row_words
unpacked = TablePacking.pack(table_field_info.row_words, table_fields_records)

for actual, expected in zip(unpacked, table_data_1):
assert actual == expected


def test_table_packing_pack_length_mismatched(
table_field_info: TableFieldInfo,
table_fields_records: Dict[str, TableFieldRecordContainer],
):
"""Test that mismatching lengths on waveform inputs causes an exception"""
assert table_field_info.row_words

# Adjust one of the record lengths so it mismatches
record_info = table_fields_records[EpicsName("OUTC1")].record_info
assert record_info
record_info.record.get = MagicMock(return_value=array([1, 2, 3, 4, 5, 6, 7, 8]))

with pytest.raises(AssertionError):
TablePacking.pack(table_field_info.row_words, table_fields_records)


def test_table_packing_roundtrip(
table_field_info: TableFieldInfo,
table_fields: Dict[str, TableFieldDetails],
table_fields_records: Dict[str, TableFieldRecordContainer],
table_data_1: List[str],
):
"""Test that calling unpack -> pack yields the same data"""
assert table_field_info.row_words
unpacked = TablePacking.unpack(
table_field_info.row_words, table_fields_records, table_data_1
)

# Put these values into Mocks for the Records
data: Dict[str, TableFieldRecordContainer] = {}
for field_name, field_info in table_fields.items():
return_value: Union[UnpackedArray, List[str]] = unpacked[field_name]
if field_info.labels:
# Convert to string representation
return_value = [field_info.labels[x] for x in return_value]

mocked_record = MagicMock()
mocked_record.get = MagicMock(return_value=return_value)
record_info = RecordInfo(lambda x: None)
record_info.add_record(mocked_record)
data[field_name] = TableFieldRecordContainer(field_info, record_info)

packed = TablePacking.pack(table_field_info.row_words, data)

assert packed == table_data_1


def test_table_updater_validate_mode_view(table_updater: TableUpdater):
"""Test the validate method when mode is View"""

Expand Down Expand Up @@ -543,9 +460,6 @@ async def test_table_updater_update_mode_submit_exception(
called_args = record_info.record.set.call_args

expected = called_args[0][0]
labels = table_updater.table_fields_records[field_name].field.labels
if labels:
expected = [labels[x] for x in expected]

numpy.testing.assert_array_equal(data, expected)

Expand Down Expand Up @@ -604,10 +518,6 @@ async def test_table_updater_update_mode_discard(

expected = called_args[0][0]

labels = table_updater.table_fields_records[field_name].field.labels
if labels:
expected = [labels[x] for x in expected]

numpy.testing.assert_array_equal(data, expected)

table_updater.mode_record_info.record.set.assert_called_once_with(
Expand Down

0 comments on commit 35360f4

Please sign in to comment.