From 07cf0dfb27883151c1192699dd58137ad4ac1d16 Mon Sep 17 00:00:00 2001 From: Eva Lott Date: Wed, 9 Oct 2024 17:29:27 +0100 Subject: [PATCH] added types for the field elements Also tried to make an `EpicsString` type capable of being used as a hash. --- src/fastcs_pandablocks/__main__.py | 12 +- src/fastcs_pandablocks/panda/blocks.py | 227 ++++++++++++++++++ .../panda/client_wrapper.py | 32 +-- src/fastcs_pandablocks/panda/panda.py | 51 ++++ src/fastcs_pandablocks/types.py | 194 +++++++-------- 5 files changed, 380 insertions(+), 136 deletions(-) create mode 100644 src/fastcs_pandablocks/panda/blocks.py diff --git a/src/fastcs_pandablocks/__main__.py b/src/fastcs_pandablocks/__main__.py index f07eaa8..e89fe3b 100644 --- a/src/fastcs_pandablocks/__main__.py +++ b/src/fastcs_pandablocks/__main__.py @@ -2,8 +2,11 @@ import argparse import logging +import asyncio + +#from fastcs_pandablocks.fastcs import ioc +from fastcs_pandablocks.panda.panda import Panda -from fastcs_pandablocks.fastcs import ioc from . import __version__ @@ -49,12 +52,19 @@ def main(): level = getattr(logging, parsed_args.log_level.upper(), None) logging.basicConfig(format="%(levelname)s:%(message)s", level=level) + async def meh(): + await Panda(parsed_args.host).connect() + asyncio.run(meh()) + + + """ ioc( parsed_args.host, parsed_args.prefix, parsed_args.screens_dir, parsed_args.clear_bobfiles, ) + """ if __name__ == "__main__": diff --git a/src/fastcs_pandablocks/panda/blocks.py b/src/fastcs_pandablocks/panda/blocks.py new file mode 100644 index 0000000..c03f4ae --- /dev/null +++ b/src/fastcs_pandablocks/panda/blocks.py @@ -0,0 +1,227 @@ +import itertools +from pprint import pprint +from typing import Type +from fastcs_pandablocks.types import EpicsName, ResponseType + +class Field: + def __init__(self, name: EpicsName, field_info: ResponseType): + self.name = name + self.field_info = field_info + +def change_value(self, new_field_value): + print("setting value", new_field_value) + self.value = new_field_value + +class TableField(Field): + ... + +class TimeField(Field): + ... + +class BitOutField(Field): + ... + +class PosOutField(Field): + ... + +class ExtOutField(Field): + ... + +class ExtOutBitsField(ExtOutField): + ... + +class BitMuxField(Field): + ... + +class PosMuxField(Field): + ... + +class UintParamField(Field): + ... + +class UintReadField(Field): + ... + +class UintWriteField(Field): + ... + +class IntParamField(Field): + ... + +class IntReadField(Field): + ... + +class IntWriteField(Field): + ... + +class ScalarParamField(Field): + ... + +class ScalarReadField(Field): + ... + +class ScalarWriteField(Field): + ... + +class BitParamField(Field): + ... + +class BitWriteField(Field): + ... + +class BitReadField(Field): + ... + +class ActionWriteField(Field): + ... + +class ActionReadField(Field): + ... + +class LutParamField(Field): + ... + +class LutWriteField(Field): + ... + +class LutReadField(Field): + ... + +class EnumParamField(Field): + ... + +class EnumWriteField(Field): + ... + +class EnumReadField(Field): + ... + +class TimeSubTypeParamField(Field): + ... + +class TimeSubTypeReadField(Field): + ... + +class TimeSubTypeWriteField(Field): + ... + +FieldType = ( + TableField + | BitParamField + | BitWriteField + | BitReadField + | ActionWriteField + | ActionReadField + | LutParamField + | LutWriteField + | LutReadField + | EnumParamField + | EnumWriteField + | EnumReadField + | TimeSubTypeParamField + | TimeSubTypeReadField + | TimeSubTypeWriteField + | TimeField + | BitOutField + | PosOutField + | ExtOutField + | ExtOutBitsField + | BitMuxField + | PosMuxField + | UintParamField + | UintReadField + | UintWriteField + | IntParamField + | IntReadField + | IntWriteField + | ScalarParamField + | ScalarReadField + | ScalarWriteField +) + +FIELD_TYPE_TO_FASTCS_TYPE: dict[str, dict[str | None, Type[FieldType]]] = { + "table": { + None: TableField + }, + "time": { + None: TimeField, + "param": TimeSubTypeParamField, + "read": TimeSubTypeReadField, + "write": TimeSubTypeWriteField, + }, + "bit_out": { + None: BitOutField, + }, + "pos_out": { + None: PosOutField, + }, + "ext_out": { + "timestamp": ExtOutField, + "samples": ExtOutField, + "bits": ExtOutBitsField, + }, + "bit_mux": { + None: BitMuxField, + }, + "pos_mux": { + None: PosMuxField, + }, + "param": { + "uint": UintParamField, + "int": IntParamField, + "scalar": ScalarParamField, + "bit": BitParamField, + "action": ActionReadField, + "lut": LutParamField, + "enum": EnumParamField, + "time": TimeSubTypeParamField, + }, + "read": { + "uint": UintReadField, + "int": IntReadField, + "scalar": ScalarReadField, + "bit": BitReadField, + "action": ActionReadField, + "lut": LutReadField, + "enum": EnumReadField, + "time": TimeSubTypeReadField, + }, + "write": { + "uint": UintWriteField, + "int": IntWriteField, + "scalar": ScalarWriteField, + "bit": BitWriteField, + "action": ActionWriteField, + "lut": LutWriteField, + "enum": EnumWriteField, + "time": TimeSubTypeWriteField, + }, +} + + +class Block: + _sub_blocks: dict[int, dict[EpicsName, FieldType]] + + def __init__(self, name: EpicsName, number: int, description: str | None, raw_fields: dict[str, ResponseType]): + self.name = name + self.number = number + self.description = description + self._sub_blocks = {} + + for number in range(1, number + 1): + numbered_block = name + EpicsName(str(number)) + single_block = self._sub_blocks[number] = {} + + for field_suffix, field_info in ( + raw_fields.items() + ): + field_name = ( + numbered_block + EpicsName(field_suffix) + ) + single_block[EpicsName(field_suffix)] = ( + FIELD_TYPE_TO_FASTCS_TYPE[field_info.type][field_info.subtype]( + field_name, field_info + ) + ) + def change_value(self, new_field_value, block_number, field_name): + self._sub_blocks[block_number][field_name].change_value(new_field_value) diff --git a/src/fastcs_pandablocks/panda/client_wrapper.py b/src/fastcs_pandablocks/panda/client_wrapper.py index ae30c76..bf05f26 100644 --- a/src/fastcs_pandablocks/panda/client_wrapper.py +++ b/src/fastcs_pandablocks/panda/client_wrapper.py @@ -14,39 +14,12 @@ GetFieldInfo, ) from pandablocks.responses import ( - BitMuxFieldInfo, - BitOutFieldInfo, BlockInfo, Changes, - EnumFieldInfo, - ExtOutBitsFieldInfo, - ExtOutFieldInfo, - FieldInfo, - PosMuxFieldInfo, - PosOutFieldInfo, - ScalarFieldInfo, - SubtypeTimeFieldInfo, - TableFieldInfo, - TimeFieldInfo, - UintFieldInfo, ) -from typing import Union -ResponseType = Union[ - BitMuxFieldInfo, - BitOutFieldInfo, - EnumFieldInfo, - ExtOutBitsFieldInfo, - ExtOutFieldInfo, - FieldInfo, - PosMuxFieldInfo, - PosOutFieldInfo, - ScalarFieldInfo, - SubtypeTimeFieldInfo, - TableFieldInfo, - TimeFieldInfo, - UintFieldInfo, -] +from fastcs_pandablocks.types import ResponseType + class RawPanda: _blocks: dict[str, BlockInfo] | None = None @@ -96,4 +69,3 @@ def __aiter__(self): async def __anext__(self): await self._ensure_connected() return await self.get_changes() - diff --git a/src/fastcs_pandablocks/panda/panda.py b/src/fastcs_pandablocks/panda/panda.py index e69de29..d7b4222 100644 --- a/src/fastcs_pandablocks/panda/panda.py +++ b/src/fastcs_pandablocks/panda/panda.py @@ -0,0 +1,51 @@ +from pprint import pprint +from typing import Callable +from dataclasses import dataclass +from .client_wrapper import RawPanda +from .blocks import Block +from fastcs_pandablocks.types import EpicsName, PandaName +from pandablocks.responses import Changes + + + +class Panda: + _raw_panda: RawPanda + _blocks: dict[EpicsName, Block] + + def __init__(self, host: str): + self._raw_panda = RawPanda(host) + self._blocks = {} + + async def connect(self): + await self._raw_panda._sync_with_panda() + self._parse_introspected_data() + + def _parse_introspected_data(self): + self._blocks = {} + if ( + self._raw_panda._blocks is None or self._raw_panda._responses is None + ): + raise ValueError("Panda not introspected.") + + for (block_name, block_info), raw_fields in zip( + self._raw_panda._blocks.items(), self._raw_panda._responses + ): + self._blocks[EpicsName(block=block_name)] = Block( + EpicsName(block_name), + block_info.number, + block_info.description, + raw_fields + ) + + + def _parse_values(self, changes: Changes): + for panda_name, field_value in changes.values.items(): + epics_name = PandaName(panda_name).to_epics_name() + self._blocks[epics_name.block].change_value( + field_value, epics_name.block_number, epics_name.field + ) + + + + + async def disconnect(self): await self._raw_panda.disconnect() diff --git a/src/fastcs_pandablocks/types.py b/src/fastcs_pandablocks/types.py index aa39194..de2c67b 100644 --- a/src/fastcs_pandablocks/types.py +++ b/src/fastcs_pandablocks/types.py @@ -1,30 +1,87 @@ +from __future__ import annotations + from dataclasses import dataclass import re -from pydantic import BaseModel -from typing import Literal - -from pydantic import BaseModel - - -# Dataclasses for names - -@dataclass +from pandablocks.responses import ( + BitMuxFieldInfo, + BitOutFieldInfo, + BlockInfo, + Changes, + EnumFieldInfo, + ExtOutBitsFieldInfo, + ExtOutFieldInfo, + FieldInfo, + PosMuxFieldInfo, + PosOutFieldInfo, + ScalarFieldInfo, + SubtypeTimeFieldInfo, + TableFieldInfo, + TimeFieldInfo, + UintFieldInfo, +) +from typing import Union + + +EPICS_SEPERATOR = ":" +PANDA_SEPERATOR = "." + +def _extract_number_at_of_string(string: str) -> tuple[str, int | None]: + pattern = r"(\D+)(\d+)$" + print("===================================================") + print(string) + match = re.match(pattern, string) + if match: + return (match.group(1), int(match.group(2))) + return string, None + + +@dataclass(frozen=True) class _Name: _name: str def __str__(self): return str(self._name) + def __repr__(self): + return str(self) class PandaName(_Name): def to_epics_name(self): - return EpicsName(self._name.replace(".", ":")) + return EpicsName(self._name.replace(PANDA_SEPERATOR, EPICS_SEPERATOR)) class EpicsName(_Name): + block: str | None = None + block_number: int | None = None + field: str | None = None + field_number: int | None = None + prefix: str | None = None + + def __init__( + self, + prefix=None, + block: str | None = None, + block_number: int | None = None, + field: str | None = None, + field_number: int | None = None + ): + self.prefix = prefix + self.block = block + self.block_number = block_number + self.field = field + self.field_number = field_number + + prefix_string = f"{self.prefix}{EPICS_SEPERATOR}" if self.prefix is not None else "" + block_number_string = f"{self.block_number}" if self.block_number is not None else "" + block_with_number = f"{self.block}{block_number_string}{EPICS_SEPERATOR}" if self.block is not None else "" + field_number_string = f"{self.field_number}" if self.field_number is not None else "" + field_with_number = f"{self.field}{field_number_string}" if self.field is not None else "" + + super().__init__(f"{prefix_string}{block_with_number}{field_with_number}") + def to_panda_name(self): - return PandaName(self._name.replace(":", ".")) + return PandaName(self._name.replace(EPICS_SEPERATOR, PANDA_SEPERATOR)) def to_pvi_name(self): - relevant_section = self._name.split(":")[-1] + relevant_section = self._name.split(EPICS_SEPERATOR)[-1] words = relevant_section.replace("-", "_").split("_") capitalised_word = "".join(word.capitalize() for word in words) @@ -33,102 +90,29 @@ def to_pvi_name(self): assert formatted_word return PviName(formatted_word.group()) - -class PviName(_Name): - ... - - - -Field_T = Literal[ - "time", - "bit_out", - "pos_out", - "ext_out", - "bit_mux", - "pos_mux", - "param", - "read", - "write", -] - -FieldSubtype_T = Literal[ - "timestamp", - "samples", - "bits", - "uint", - "int", - "scalar", - "bit", - "action", - "lut", - "enum", - "time", -] + + def __add__(self, suffix: "EpicsName"): + return EpicsName(f"{str(self)}{EPICS_SEPERATOR}{str(suffix)}") -class PandaField(BaseModel, frozen=True): - """Validates fields from the client.""" - field_type: Field_T - field_subtype: FieldSubtype_T | None +class PviName(_Name): + ... -TIME_FIELDS = { - PandaField(field_type="time", field_subtype=None), -} - -BIT_OUT_FIELDS = { - PandaField(field_type="bit_out", field_subtype=None), -} - -POS_OUT_FIELDS = { - PandaField(field_type="pos_out", field_subtype=None), -} - -EXT_OUT_FIELDS = { - PandaField(field_type="ext_out", field_subtype="timestamp"), - PandaField(field_type="ext_out", field_subtype="samples"), -} - -EXT_OUT_BITS_FIELDS = { - PandaField(field_type="ext_out", field_subtype="bits"), -} - -BIT_MUX_FIELDS = { - PandaField(field_type="bit_mux", field_subtype=None), -} - -POS_MUX_FIELDS = { - PandaField(field_type="pos_mux", field_subtype=None), -} - -UINT_FIELDS = { - PandaField(field_type="param", field_subtype="uint"), - PandaField(field_type="read", field_subtype="uint"), - PandaField(field_type="write", field_subtype="uint"), -} - -INT_FIELDS = { - PandaField(field_type="param", field_subtype="int"), - PandaField(field_type="read", field_subtype="int"), - PandaField(field_type="write", field_subtype="int"), -} - -SCALAR_FIELDS = { - PandaField(field_type="param", field_subtype="scalar"), - PandaField(field_type="read", field_subtype="scalar"), - PandaField(field_type="write", field_subtype="scalar"), -} - -BIT_FIELDS = { - PandaField(field_type="param", field_subtype="bit"), - PandaField(field_type="read", field_subtype="bit"), - PandaField(field_type="write", field_subtype="bit"), -} - -ACTION_FIELDS = { - PandaField(field_type="param", field_subtype="action"), - PandaField(field_type="read", field_subtype="action"), - PandaField(field_type="write", field_subtype="action"), -} +ResponseType = Union[ + BitMuxFieldInfo, + BitOutFieldInfo, + EnumFieldInfo, + ExtOutBitsFieldInfo, + ExtOutFieldInfo, + FieldInfo, + PosMuxFieldInfo, + PosOutFieldInfo, + ScalarFieldInfo, + SubtypeTimeFieldInfo, + TableFieldInfo, + TimeFieldInfo, + UintFieldInfo, +]