From 1bf48ceb2d5ebb09ed6c34dadbc552e3ed2c9290 Mon Sep 17 00:00:00 2001 From: Rene Reimann Date: Tue, 29 Jul 2025 16:35:59 +0200 Subject: [PATCH 1/5] Defining Pfeiffer endpoints that use the pfeiffer telegram protocol. We assemble and the command at each get / set and disensemble / format the response corresponding to the datatype defined by pfeiffer. --- dripline/extensions/__init__.py | 1 + dripline/extensions/pfeiffer_endpoint.py | 137 +++++++++++++++++++++++ 2 files changed, 138 insertions(+) create mode 100644 dripline/extensions/pfeiffer_endpoint.py diff --git a/dripline/extensions/__init__.py b/dripline/extensions/__init__.py index e9cb836..8582afc 100644 --- a/dripline/extensions/__init__.py +++ b/dripline/extensions/__init__.py @@ -10,3 +10,4 @@ from .add_auth_spec import * from .thermo_fisher_endpoint import * from .ethernet_thermo_fisher_service import * +from .pfeiffer_endpoint import * diff --git a/dripline/extensions/pfeiffer_endpoint.py b/dripline/extensions/pfeiffer_endpoint.py new file mode 100644 index 0000000..289ded9 --- /dev/null +++ b/dripline/extensions/pfeiffer_endpoint.py @@ -0,0 +1,137 @@ +from dripline.core import Entity, calibrate, ThrowReply + +import logging +logger = logging.getLogger(__name__) + +__all__ = [] + +__all__.append('PfeifferEntity') +class PfeifferEntity(Entity): + ''' + ''' + + _datatype = {"bool_old": 6, "uint": 6, "ureal": 6, "string": 6, "bool": 1, "ushort": 3, "uexpo": 6, "str16": 16, "str8": 8, "query": 2} + + def __init__(self, + parameter = 303, + datatype = "uexpo", + unit_address = 1, + **kwargs): + ''' + Args: + parameter (int): number of the parameter as documented in the manual" + datatype (str): one of ["bool_old", "uint", "ureal", "string", "bool", "ushort", "uexpo", "str16", "str8" + unit_address (int): number of the unit address, allwoed range: 1-16 + ''' + Entity.__init__(self, **kwargs) + self.parameter = parameter + self.datatype = datatype + self.unit_address = unit_address + + def get_checksum(self, string): + return sum([ord(c) for c in string])%256 + + def format_value(self, value): + if self.datatype == "bool_old": + return "111111" if value else "000000" + if self.datatype == "bool": + return "1" if value else "0" + if self.datatype == "string": + return f"{value:6s}" + if self.datatype == "str8": + return f"{value:8s}" + if self.datatype == "str16": + return f"{value:16s}" + if self.datatype == "uint": + return f"{value:06d}" + if self.datatype == "ushort": + return f"{value:03d}" + if self.datatype == "ureal": + val = int(value*100) + return f"{val:06d}" + if self.datatype == "uexpo": + expon = int(np.log10(val)//1) + val = int(val/10**expon*1000) + expon_mod = expon - 20 + return f"{val:04d}{expon_mod:02d}" + + def unformat_value(self, value): + if value in ["NO_DEF", "_RANGE", "_LOGIC"]: + raise ValueError(f"Device responded with an error {value}") + if len(value) != self._datatype[self.datatype]: + raise ValueError(f"data does not match length of datatype") + + if self.datatype == "bool_old": + return value == "111111" + if self.datatype == "bool": + return value == 1 + if self.datatype in ["string", "str16", "str8"]: + return value + if self.datatype in ["uint", "ushort"]: + return int(value) + if self.datatype == "ureal": + return int(value)/100. + if self.datatype == "uexpo": + return int(value[:4])/1000. * 10**(int(value[4:]) - 20) + + def disensemble_result(self, reply): + if self.get_checksum(reply[:-3]) != int(reply[-3:]): + logger.debug("checksum not matching") + address = int(reply[:3]) + action = reply[3:5] + parameter = int(reply[5:8]) + length = int(reply[8:10]) + data = reply[10:10+length] + return data + + @calibrate() + def on_get(self): + value = "=?" + cmd = f"{self.unit_address:03d}00{self.parameter:03d}{len(value):02d}{value}" + cs = self.get_checksum(cmd) + cmd += f"{cs:03d}" + result = self.service.send_to_device([cmd]) + logger.debug(f'raw result is: {result}') + result = self.disensemble_result(result) + logger.debug(f'disensembled result is: {result}') + result = self.unformat_value(result) + logger.debug(f'unformated result is: {result}') + return result + + def on_set(self, value): + value = self.format_value(value) + cmd = f"{self.unit_address:03d}10{self.parameter:03d}{len(value):02d}{value}" + cs = self.get_checksum(cmd) + cmd += f"{cs:03d}" + result = self.service.send_to_device([cmd]) + logger.debug(f'raw result is: {result}') + result = self.disensemble_result(result) + logger.debug(f'disensembled result is: {result}') + result = self.unformat_value(result) + logger.debug(f'unformated result is: {result}') + return result + +__all__.append('PfeifferGetEntity') +class PfeifferGetEntity(PfeifferEntity): + ''' + Identical to PfeifferEntity, but with an explicit exception if on_set is attempted + ''' + + def __init__(self, **kwargs): + PfeifferEntity.__init__(self, **kwargs) + + def on_set(self, value): + raise ThrowReply('message_error_invalid_method', f"endpoint '{self.name}' does not support set") + + +__all__.append('PfeifferSetEntity') +class PfeifferSetEntity(PfeifferEntity): + ''' + Modelled on PfeifferEntity, but with an explicit exception if on_get is attempted. + ''' + + def __init__(self, **kwargs): + PfeifferEntity.__init__(self, **kwargs) + + def on_get(self): + raise ThrowReply('message_error_invalid_method', f"endpoint '{self.name}' does not support get") From 71c4d772cdc382b1385754de0a02a6d387faafe5 Mon Sep 17 00:00:00 2001 From: Rene Reimann Date: Tue, 29 Jul 2025 16:37:12 +0200 Subject: [PATCH 2/5] example config file to show how Pfeiffer Endpoints are used --- examples/pressure_gauge_60.yaml | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 examples/pressure_gauge_60.yaml diff --git a/examples/pressure_gauge_60.yaml b/examples/pressure_gauge_60.yaml new file mode 100644 index 0000000..a70c4b1 --- /dev/null +++ b/examples/pressure_gauge_60.yaml @@ -0,0 +1,28 @@ +runtime-config: + name: pressure_gauge_60 + module: EthernetSCPIService + #module: EthernetPfeifferService + socket_timeout: 5 + socket_info: ("10.93.130.113", 10001) # astro-pirate + cmd_at_reconnect: + - "0010034902=?111" # Check for model name + reconnect_test: "0011034906HPT200118" + command_terminator: "\r" # is what goes after the command sent + response_terminator: "\r" # is what goes after the command sent + endpoints: + - name: pg60_error_status + module: PfeifferGetEntity + parameter: 303 + datatype: string + log_interval: 10 + + - name: pg60_pressure_mbar + module: PfeifferEntity + parameter: 740 + datatype: uexpo + log_interval: 10 + + - name : pg60_goal_degas + module: PfeifferEntity + parameter: 40 + datatype: bool From fcecc095dbc1f2a05f706dde20589cd48226c837 Mon Sep 17 00:00:00 2001 From: Rene Reimann Date: Tue, 29 Jul 2025 16:50:46 +0200 Subject: [PATCH 3/5] improve doc strings --- dripline/extensions/pfeiffer_endpoint.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/dripline/extensions/pfeiffer_endpoint.py b/dripline/extensions/pfeiffer_endpoint.py index 289ded9..e71fca5 100644 --- a/dripline/extensions/pfeiffer_endpoint.py +++ b/dripline/extensions/pfeiffer_endpoint.py @@ -8,6 +8,9 @@ __all__.append('PfeifferEntity') class PfeifferEntity(Entity): ''' + Pfeiffer Entity implements the Telegram protocol by Pfeiffer. + The message contains commands in form of parameter numbers and different length data that depend on the given datatype. + Conversion of datatypes and checksum checking are implemented. ''' _datatype = {"bool_old": 6, "uint": 6, "ureal": 6, "string": 6, "bool": 1, "ushort": 3, "uexpo": 6, "str16": 16, "str8": 8, "query": 2} From 9affd8c50a326b36a5a6122901503ada9d3e6d92 Mon Sep 17 00:00:00 2001 From: Rene Reimann Date: Wed, 30 Jul 2025 10:09:06 +0200 Subject: [PATCH 4/5] using develop-dev as base container --- Dockerfile | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 27455bb..1ff0f10 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,6 @@ ARG img_user=ghcr.io/driplineorg ARG img_repo=dripline-python -#ARG img_tag=develop-dev -ARG img_tag=receiver-test +ARG img_tag=develop-dev FROM ${img_user}/${img_repo}:${img_tag} From ced66d133a436d23c68703b02fc5b19302c62171 Mon Sep 17 00:00:00 2001 From: Rene Reimann Date: Mon, 6 Oct 2025 15:12:25 +0200 Subject: [PATCH 5/5] update to recent dripline version --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 1ff0f10..c4a291f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ ARG img_user=ghcr.io/driplineorg ARG img_repo=dripline-python -ARG img_tag=develop-dev +ARG img_tag=v5.1.0 FROM ${img_user}/${img_repo}:${img_tag}