# This file is part of icspacket.
# Copyright (C) 2025-present MatrixEditor @ github
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
"""[ODVA CIP Vol 1] Wrappers for the "Hierarchy of Semiconductor Equipment
Devices" object family:
- S-Device Supervisor (class 0x30, §5-35),
- S-Analog Sensor (class 0x31, §5-36),
- S-Analog Actuator (class 0x32, §5-37),
- S-Single Stage Controller (class 0x33, §5-38),
- S-Gas Calibration (class 0x34, §5-39),
- Trip Point (class 0x35, §5-40),
- S-Partial Pressure (class 0x38, §5-43), and
- S-Sensor Calibration (class 0x40, §5-44).
Every object in this family is managed by the S-Device Supervisor Object
and shares its "Subclass" extensibility mechanism: instance/class
attribute 99 reports whether a subclass is active (0 = none) and, if so,
IDs 81-96 (instance) / 96 (class) and downward are redefined by that
subclass. Subclass-specific attributes/services (e.g. S-Device
Supervisor's "Power Generator" subclass) are vendor/profile specific and
out of scope here, consistent with this library's treatment of other
device-profile extensions.
"""
from collections.abc import Collection
from typing import Any, ClassVar
from caterpillar.fields import (
float32,
float64,
int8,
int16,
int32,
int64,
uint8,
uint16,
uint32,
uint64,
)
from caterpillar.model import StructDefMixin
from caterpillar.py import pack, unpack
from caterpillar.shortcuts import LittleEndian, struct
from caterpillar.types import float32_t, uint8_t, uint16_t, uint32_t
from ..const import ClassCode, CommonService
from ..epath import EPATH
from ._base import CIP_EPATH, CIP_SHORT_STRING, CIPAttribute, CIPObject
__all__ = [
"ELEMENTARY_DATA_TYPES",
"CIPDateAndTime",
"CreateRangeRequest",
"CreateRangeResult",
"FullScale",
"GasCalibrationEntry",
"GroupEnableRequest",
"PartialPressureInstanceEntry",
"PartialPressureReading",
"SAnalogActuatorObject",
"SAnalogSensorObject",
"SDeviceSupervisorObject",
"SGasCalibrationObject",
"SPartialPressureObject",
"SSensorCalibrationObject",
"SSingleStageControllerObject",
"SensorCalibrationEntry",
"TripPointObject",
"decode_elementary_value",
"encode_elementary_value",
]
[docs]
@struct(order=LittleEndian)
class CIPDateAndTime(StructDefMixin):
"""CIP ``DATE_AND_TIME`` value (See CIP Vol 1, Appendix C, C-6.2)."""
time_of_day: uint32_t
"""Milliseconds since midnight."""
date: uint16_t
"""Days since 1972-01-01."""
#: CIP Elementary Data Type code (Appendix C-6.1, Table C-6.1) -> wire
#: schema. Used by the S-Analog Sensor/Actuator and S-Single Stage
#: Controller "Data Type"/"Gain Data Type"/"CV Data Type" attributes to
#: select the wire type of their Data-Type-dependent companion
#: attributes. Only the numeric elementary types are mapped; BOOL and
#: non-numeric types (STRING, BYTE/WORD/..., STIME/DATE/...) have no
#: fixed numeric meaning here and decode to raw bytes.
ELEMENTARY_DATA_TYPES: dict[int, Any] = {
0xC2: int8, # SINT
0xC3: int16, # INT
0xC4: int32, # DINT
0xC5: int64, # LINT
0xC6: uint8, # USINT
0xC7: uint16, # UINT
0xC8: uint32, # UDINT
0xC9: uint64, # ULINT
0xCA: float32, # REAL
0xCB: float64, # LREAL
}
[docs]
def decode_elementary_value(data: bytes, data_type: int) -> int | float | bytes:
"""Decode a Data-Type-dependent attribute payload using its companion Data
Type attribute's Appendix C-6.1 code."""
schema = ELEMENTARY_DATA_TYPES.get(data_type)
return unpack(schema, data, order=LittleEndian) if schema is not None else data
[docs]
def encode_elementary_value(value: int | float | bytes, data_type: int) -> bytes:
"""Encode a Data-Type-dependent attribute payload using its companion Data
Type attribute's Appendix C-6.1 code."""
schema = ELEMENTARY_DATA_TYPES.get(data_type)
return (
pack(value, schema, order=LittleEndian) if schema is not None else bytes(value)
)
[docs]
@struct(order=LittleEndian)
class FullScale(StructDefMixin):
"""Full Scale amount + ENGUNITS code pair, shared by the S-Gas Calibration
and S-Sensor Calibration objects' ``full_scale`` attribute (See
§5-39.2/§5-44.3, Table 5-39.2/5-44.2)."""
amount: float32_t
"""The amount of measured parameter corresponding to full scale."""
units: uint16_t
"""ENGUNITS code for ``amount`` (See Appendix D)."""
[docs]
@struct(order=LittleEndian)
class GasCalibrationEntry(StructDefMixin):
"""One S-Gas Calibration Get_All_Instances response element (See §5-39.5,
Table 5-39.5)."""
instance_id: uint16_t
gas_standard_number: uint16_t
valid_sensor_instance: uint16_t
[docs]
@struct(order=LittleEndian)
class SensorCalibrationEntry(StructDefMixin):
"""One S-Sensor Calibration Get_All_Instances response element (See
§5-44.7.2, Table 5-44.5)."""
instance_id: uint16_t
calibration_id_number: uint16_t
valid_sensor_instance: uint16_t
[docs]
@struct(order=LittleEndian)
class PartialPressureInstanceEntry(StructDefMixin):
"""One S-Partial Pressure Get_Instance_List response element (See
§5-43.4.2, Table 5-43.11)."""
instance_number: uint16_t
instance_type: uint8_t
amu: float32_t
ending_amu: float32_t
gas_standard_number: uint16_t
[docs]
@struct(order=LittleEndian)
class PartialPressureReading(StructDefMixin):
"""One Get_Pressures/Get_All_Pressures response element (See §5-43.4.3/
§5-43.4.4, Table 5-43.12/5-43.13)."""
instance_id: uint16_t
partial_pressure: float32_t
[docs]
@struct(order=LittleEndian)
class CreateRangeRequest(StructDefMixin):
"""Create_Range (0x4B) request parameters (See §5-43.4.1, Table 5-43.9)."""
start_amu: float32_t
end_amu: float32_t
num_instances: uint16_t
starting_id: uint16_t
"""First instance ID to create; 0 = use next available."""
group_id: uint8_t
"""Group value assigned to the created instances."""
[docs]
@struct(order=LittleEndian)
class CreateRangeResult(StructDefMixin):
"""Create_Range (0x4B) success response (See §5-43.4.1, Table 5-43.10)."""
starting_id: uint16_t
ending_id: uint16_t
[docs]
@struct(order=LittleEndian)
class GroupEnableRequest(StructDefMixin):
"""Group_Enable (0x4F) request parameters (See §5-43.4.5, Table
5-43.14)."""
enable: uint8_t
"""0 = Disable, 1 = Enable."""
group_id: uint8_t
"""Affects every instance whose ``group_id`` attribute has this value."""
[docs]
class SDeviceSupervisorObject(CIPObject):
"""Manages application object state, exceptions and behavior shared by
every object in the Hierarchy of Semiconductor Equipment Devices (See CIP
Vol 1, §5-35).
Class attr 1 (Revision, fixed at 2) and class attr 99 (Subclass) are
available via :meth:`get_class_attribute`. ``exception_detail_alarm``/
``exception_detail_warning`` (attributes 13/14) are each a STRUCT of
three (Size:USINT, Detail:BYTE[Size]) sub-structures - Common,
Device-specific and Manufacturer-specific - and are kept as raw bytes
due to that nested variable-length shape.
"""
CLASS_CODE: ClassVar[ClassCode] = ClassCode.S_DEVICE_SUPERVISOR
number_of_attributes: CIPAttribute[int] = CIPAttribute(1, uint8)
"""Number of attributes supported by the object instance (attribute 1)."""
attribute_list: CIPAttribute[Collection[int]] = CIPAttribute(2, uint8[...])
"""List of attributes supported by the object instance (attribute 2)."""
device_type: CIPAttribute[str] = CIPAttribute(3, CIP_SHORT_STRING)
"""Specific Device Model within the SEMI S/A hierarchy, max 8 characters
(attribute 3, required)."""
semi_standard_revision_level: CIPAttribute[str] = CIPAttribute(4, CIP_SHORT_STRING)
"""SEMI S/A Network Standard revision, e.g. ``"E54-0997"`` (attribute 4,
required)."""
manufacturer_name: CIPAttribute[str] = CIPAttribute(5, CIP_SHORT_STRING)
"""Max 20 characters (attribute 5, required)."""
manufacturer_model_number: CIPAttribute[str] = CIPAttribute(6, CIP_SHORT_STRING)
"""Max 20 characters, manufacturer specified (attribute 6, required)."""
software_revision_level: CIPAttribute[str] = CIPAttribute(7, CIP_SHORT_STRING)
"""Max 6 characters (attribute 7, required)."""
hardware_revision_level: CIPAttribute[str] = CIPAttribute(8, CIP_SHORT_STRING)
"""Max 6 characters (attribute 8, required)."""
manufacturer_serial_number: CIPAttribute[str] = CIPAttribute(9, CIP_SHORT_STRING)
"""Max 30 characters, manufacturer specified (attribute 9)."""
device_configuration: CIPAttribute[str] = CIPAttribute(10, CIP_SHORT_STRING)
"""Max 50 characters, manufacturer specified free-form configuration info
(attribute 10)."""
device_status: CIPAttribute[int] = CIPAttribute(11, uint8)
"""0=Undefined, 1=Self Testing, 2=Idle, 3=Self-Test Exception, 4=Executing,
5=Abort, 6=Critical Fault, 51-99=Device Specific, 100-255=Vendor Specific
(attribute 11, required)."""
exception_status: CIPAttribute[int] = CIPAttribute(12, uint8)
"""Bit 7=0: Basic method, device/profile specific bits 0-6.
Bit 7=1: Expanded method - bit 0=common alarm, 1=device alarm, 2=mfg alarm,
4=common warning, 5=device warning, 6=mfg warning (attribute 12,
required).
"""
exception_detail_alarm: CIPAttribute[bytes] = CIPAttribute(13)
"""Common/Device/Manufacturer alarm detail bitmaps; present only if
Expanded method and exception_status bit 7 conditions apply (attribute
13)."""
exception_detail_warning: CIPAttribute[bytes] = CIPAttribute(14)
"""Common/Device/Manufacturer warning detail bitmaps, same shape as
exception_detail_alarm (attribute 14)."""
alarm_enable: CIPAttribute[int] = CIPAttribute(15, uint8)
"""Enables alarm exception reporting (attribute 15, required)."""
warning_enable: CIPAttribute[int] = CIPAttribute(16, uint8)
"""Enables warning exception reporting (attribute 16, required)."""
time: CIPAttribute[CIPDateAndTime] = CIPAttribute(17, CIPDateAndTime)
"""The device's internal realtime clock (attribute 17)."""
clock_power_cycle_behavior: CIPAttribute[int] = CIPAttribute(18, uint8)
"""0=clock always resets on power cycle [default], 1=stored in NV memory at
power down, 2=battery-backed and free-running (attribute 18)."""
last_maintenance_date: CIPAttribute[int] = CIPAttribute(19, uint16)
"""DATE the device was last serviced (attribute 19)."""
next_scheduled_maintenance_date: CIPAttribute[int] = CIPAttribute(20, uint16)
"""DATE the device should next be serviced (attribute 20)."""
scheduled_maintenance_expiration_timer: CIPAttribute[int] = CIPAttribute(21, int16)
"""Countdown to the next scheduled maintenance (attribute 21)."""
scheduled_maintenance_expiration_warning_enable: CIPAttribute[int] = CIPAttribute(
22, uint8
)
"""Enables a warning when scheduled_maintenance_expiration_timer expires;
required if Calibration Expiration is supported (attribute 22)."""
run_hours: CIPAttribute[int] = CIPAttribute(23, uint32)
"""Hours the device has had power applied, resolution 1 hour, stored in NV
memory (attribute 23)."""
endpoint: CIPAttribute[int] = CIPAttribute(24, uint8)
"""0=Endpoint not obtained, 1=Endpoint obtained by the current recipe
step's algorithm (attribute 24)."""
recipe: CIPAttribute[int] = CIPAttribute(25, uint16)
"""Selects the endpoint algorithm to use (attribute 25)."""
subclass: CIPAttribute[int] = CIPAttribute(99, uint16)
"""0=No subclass, 1=Power Generator (attribute 99)."""
[docs]
def get_class_attribute(self, attribute: int) -> bytes:
"""Read an S-Device Supervisor class attribute from instance 0 (See
§5-35.1)."""
return self.get_attr(attribute, instance=0)
[docs]
def reset(self) -> bytes:
"""Invoke Reset, returning the device to the Self-Testing state (See
§5-35.4)."""
return self._expect_empty(self.message(CommonService.RESET))
[docs]
def start(self) -> bytes:
"""Invoke Start, moving the device to the Executing state (See
§5-35.4)."""
return self._expect_empty(self.message(CommonService.START))
[docs]
def stop(self) -> bytes:
"""Invoke Stop, moving the device to the Idle state (optional, See
§5-35.4)."""
return self._expect_empty(self.message(CommonService.STOP))
[docs]
def abort(self) -> bytes:
"""Invoke Abort (0x4B), moving the device to the Abort state (See
§5-35.5.1)."""
return self._expect_empty(self.message(0x4B))
[docs]
def recover(self) -> bytes:
"""Invoke Recover (0x4C), moving the device out of the Abort state to
Idle (See §5-35.5.2)."""
return self._expect_empty(self.message(0x4C))
[docs]
class SAnalogSensorObject(CIPObject):
"""Models an analog sensor reading in the Hierarchy of Semiconductor
Equipment Devices (See CIP Vol 1, §5-36).
``value`` and its related attributes (``full_scale``, ``offset_a``,
``offset_b``, ``gain``, ``unity_gain_reference``, the alarm/warning
trip points and hysteresis, ``safe_value``, ``overrange``,
``underrange`` and ``produce_trigger_delta``) are all "INT or
specified by Data Type" - their wire type is only known at runtime
from ``data_type``/``offset_a_data_type``/``gain_data_type``, so they
are exposed as raw bytes; use :func:`decode_elementary_value`/
:func:`encode_elementary_value` (or :meth:`read_value`/
:meth:`write_value` for ``value`` itself) once the relevant Data Type
attribute is known.
"""
CLASS_CODE: ClassVar[ClassCode] = ClassCode.S_ANALOG_SENSOR
number_of_attributes: CIPAttribute[int] = CIPAttribute(1, uint8)
"""Number of attributes supported by this object instance (attribute 1)."""
attribute_list: CIPAttribute[Collection[int]] = CIPAttribute(2, uint8[...])
"""List of attributes supported by this object instance (attribute 2)."""
data_type: CIPAttribute[int] = CIPAttribute(3, uint8)
"""Appendix C-6.1 code selecting the wire type of ``value`` and related
attributes; settable only in the Idle state (attribute 3)."""
data_units: CIPAttribute[bytes] = CIPAttribute(4)
"""ENGUNITS context of ``value`` and related attributes, settable only in
the Idle state (attribute 4, See Appendix D)."""
reading_valid: CIPAttribute[int] = CIPAttribute(5, uint8)
"""0=Value is valid, 1=invalid (attribute 5)."""
value: CIPAttribute[bytes] = CIPAttribute(6)
"""The corrected sensor reading: Value = Gain * (Sensor Reading +
Offset-A) + Offset-B (attribute 6, required)."""
status: CIPAttribute[int] = CIPAttribute(7, uint8)
"""Bit 0=High Alarm, 1=Low Alarm, 2=High Warning, 3=Low Warning (attribute
7, required)."""
alarm_enable: CIPAttribute[int] = CIPAttribute(8, uint8)
"""Enables setting the alarm status bits (attribute 8)."""
warning_enable: CIPAttribute[int] = CIPAttribute(9, uint8)
"""Enables setting the warning status bits (attribute 9)."""
full_scale: CIPAttribute[bytes] = CIPAttribute(10)
"""The full-scale value, in the same data type/units as ``value``
(attribute 10)."""
offset_a_data_type: CIPAttribute[int] = CIPAttribute(11, uint8)
"""Appendix C-6.1 code for ``offset_a``, if it is not the same type as
``value`` (attribute 11)."""
offset_a: CIPAttribute[bytes] = CIPAttribute(12)
"""Amount added to the raw sensor reading before ``gain`` is applied
(attribute 12)."""
gain_data_type: CIPAttribute[int] = CIPAttribute(13, uint8)
"""Appendix C-6.1 code for ``gain``/``unity_gain_reference``, required if
``gain`` is not REAL (attribute 13)."""
gain: CIPAttribute[bytes] = CIPAttribute(14)
"""Multiplier applied to (Sensor Reading + Offset-A); REAL unless
gain_data_type says otherwise, default 1.0 (attribute 14)."""
unity_gain_reference: CIPAttribute[bytes] = CIPAttribute(15)
"""Value of ``gain`` equivalent to a gain of 1.0, used to normalize it
(attribute 15)."""
offset_b: CIPAttribute[bytes] = CIPAttribute(16)
"""Amount added to Value after ``gain`` is applied, default 0 (attribute
16)."""
alarm_trip_point_high: CIPAttribute[bytes] = CIPAttribute(17)
"""Value above which a High Alarm occurs, default = max for the data
type (attribute 17)."""
alarm_trip_point_low: CIPAttribute[bytes] = CIPAttribute(18)
"""Value below which a Low Alarm occurs, default = min for the data type
(attribute 18)."""
alarm_hysteresis: CIPAttribute[bytes] = CIPAttribute(19)
"""Recovery margin to clear an Alarm condition, default 0 (attribute
19)."""
alarm_settling_time: CIPAttribute[int] = CIPAttribute(20, uint16)
"""Milliseconds a trip condition must persist before the Alarm bit is set
(attribute 20)."""
warning_trip_point_high: CIPAttribute[bytes] = CIPAttribute(21)
"""Value above which a High Warning occurs (attribute 21)."""
warning_trip_point_low: CIPAttribute[bytes] = CIPAttribute(22)
"""Value below which a Low Warning occurs (attribute 22)."""
warning_hysteresis: CIPAttribute[bytes] = CIPAttribute(23)
"""Recovery margin to clear a Warning condition, default 0 (attribute
23)."""
warning_settling_time: CIPAttribute[int] = CIPAttribute(24, uint16)
"""Milliseconds a trip condition must persist before the Warning bit is set
(attribute 24)."""
safe_state: CIPAttribute[int] = CIPAttribute(25, uint8)
"""Behavior for states other than Executing, default 0 (attribute 25)."""
safe_value: CIPAttribute[bytes] = CIPAttribute(26)
"""Value used for safe_state = Use Safe Value, default 0 (attribute
26)."""
autozero_enable: CIPAttribute[int] = CIPAttribute(27, uint8)
"""Enables an automatic zero-adjust cycle (attribute 27)."""
autozero_status: CIPAttribute[int] = CIPAttribute(28, uint8)
"""Status of the autozero_enable cycle (attribute 28)."""
autorange_enable: CIPAttribute[int] = CIPAttribute(29, uint8)
"""Enables automatic range selection (attribute 29)."""
range_multiplier: CIPAttribute[float] = CIPAttribute(30, float32)
"""Current multiplier applied by the autorange_enable mechanism (attribute
30)."""
averaging_time: CIPAttribute[int] = CIPAttribute(31, uint16)
"""Milliseconds over which readings are averaged (attribute 31)."""
overrange: CIPAttribute[bytes] = CIPAttribute(32)
"""Value above which Reading Valid is set to invalid, default = max for
the data type (attribute 32)."""
underrange: CIPAttribute[bytes] = CIPAttribute(33)
"""Value below which Reading Valid is set to invalid, default = min for
the data type (attribute 33)."""
produce_trigger_delta: CIPAttribute[bytes] = CIPAttribute(34)
"""Change in Value required to trigger a Change-of-State production; 0 =
disabled (attribute 34)."""
calibration_object_instance: CIPAttribute[int] = CIPAttribute(35, uint16)
"""S-Sensor Calibration object instance active for this instance; 0 =
disabled (attribute 35)."""
produce_trigger_delta_type: CIPAttribute[int] = CIPAttribute(36, uint8)
"""Appendix C-6.1 code for produce_trigger_delta, if not the same type as
``value`` (attribute 36)."""
value_descriptor: CIPAttribute[str] = CIPAttribute(37, CIP_SHORT_STRING)
"""User defined name of the sensed parameter, max 20 characters (attribute
37)."""
subclass: CIPAttribute[int] = CIPAttribute(99, uint16)
"""0=No subclass, 1=Flow Diagnostics, 2=Heat Transfer Vacuum Gauge,
3=Capacitance Manometer, 4=Cold Cathode Ion Gauge, 5=Hot Cathode Ion Gauge,
6=Transfer Function (attribute 99)."""
[docs]
def read_value(self) -> int | float | bytes:
"""Read and decode ``value`` according to the current ``data_type``."""
return decode_elementary_value(self.value, self.data_type)
[docs]
def write_value(self, value: int | float | bytes) -> bytes:
"""Encode and write ``value`` according to the current
``data_type``."""
return self.set_empty(6, encode_elementary_value(value, self.data_type))
[docs]
def zero_adjust(self, target_value: int | float | bytes = 0) -> bytes:
"""Invoke Zero_Adjust (0x4B): asks the device to modify Offset-A and/or
Offset-B so that ``value`` equals ``target_value``, encoded per
``data_type`` (default 0, See §5-36.5.1)."""
request = encode_elementary_value(target_value, self.data_type)
return self._expect_empty(self.message(0x4B, request))
[docs]
def gain_adjust(self, target_value: int | float | bytes) -> bytes:
"""Invoke Gain_Adjust (0x4C): asks the device to modify ``gain`` so
that ``value`` equals ``target_value``, encoded per ``data_type`` (See
§5-36.5.1)."""
request = encode_elementary_value(target_value, self.data_type)
return self._expect_empty(self.message(0x4C, request))
[docs]
class SAnalogActuatorObject(CIPObject):
"""Models an analog actuator drive signal in the Hierarchy of Semiconductor
Equipment Devices (See CIP Vol 1, §5-37).
ZERO corresponds to the powered-off/not-actuated state, which may or
may not equal the physically de-energized position depending on
device wiring. ``value`` and its related attributes are all "INT or
specified by Data Type" and, as with :class:`SAnalogSensorObject`,
are exposed as raw bytes with :meth:`read_value`/:meth:`write_value`
provided for ``value`` itself. This object has no object-specific
services; its behavior is entirely managed by
:class:`SDeviceSupervisorObject`.
"""
CLASS_CODE: ClassVar[ClassCode] = ClassCode.S_ANALOG_ACTUATOR
number_of_attributes: CIPAttribute[int] = CIPAttribute(1, uint8)
"""Number of attributes supported by this object instance (attribute 1)."""
attribute_list: CIPAttribute[Collection[int]] = CIPAttribute(2, uint8[...])
"""List of attributes supported by this object instance (attribute 2)."""
data_type: CIPAttribute[int] = CIPAttribute(3, uint8)
"""Appendix C-6.1 code selecting the wire type of ``value`` and related
attributes; settable only in the Idle state (attribute 3)."""
data_units: CIPAttribute[bytes] = CIPAttribute(4)
"""ENGUNITS context of ``value``, settable only in the Idle state
(attribute 4, See Appendix D)."""
override: CIPAttribute[int] = CIPAttribute(5, uint8)
"""0=normal [default]; any other value overrides the physical actuator and
``value`` is ignored (attribute 5, required)."""
value: CIPAttribute[bytes] = CIPAttribute(6)
"""The uncorrected analog output value, default 0 (attribute 6,
required)."""
status: CIPAttribute[int] = CIPAttribute(7, uint8)
"""Alarm/Warning state of this object instance, default 0 (attribute 7,
required)."""
alarm_enable: CIPAttribute[int] = CIPAttribute(8, uint8)
"""Enables setting the alarm status bit, default disabled (attribute 8)."""
warning_enable: CIPAttribute[int] = CIPAttribute(9, uint8)
"""Enables setting the warning status bit, default disabled (attribute
9)."""
offset: CIPAttribute[bytes] = CIPAttribute(10)
"""Amount added to Value prior to the application of gain, default 0
(attribute 10)."""
bias: CIPAttribute[bytes] = CIPAttribute(11)
"""Amount added to Value after the application of gain, default 0
(attribute 11)."""
gain_data_type: CIPAttribute[int] = CIPAttribute(12, uint8)
"""Appendix C-6.1 code for ``gain``/``unity_gain_reference``, required if
``gain`` is not REAL (attribute 12)."""
gain: CIPAttribute[bytes] = CIPAttribute(13)
"""Scales Value prior to driving the physical actuator; REAL unless
gain_data_type says otherwise, default 1.0 (attribute 13)."""
unity_gain_reference: CIPAttribute[bytes] = CIPAttribute(14)
"""Value of ``gain`` equivalent to a gain of 1.0, default 1.0 (attribute
14)."""
alarm_trip_point_high: CIPAttribute[bytes] = CIPAttribute(15)
"""Value above which an Alarm occurs, default = max for the data type
(attribute 15)."""
alarm_trip_point_low: CIPAttribute[bytes] = CIPAttribute(16)
"""Value below which an Alarm occurs, default = min for the data type
(attribute 16)."""
alarm_hysteresis: CIPAttribute[bytes] = CIPAttribute(17)
"""Recovery margin to clear an Alarm condition, default 0 (attribute
17)."""
warning_trip_point_high: CIPAttribute[bytes] = CIPAttribute(18)
"""Value above which a Warning occurs, default = max for the data type
(attribute 18)."""
warning_trip_point_low: CIPAttribute[bytes] = CIPAttribute(19)
"""Value below which a Warning occurs, default = min for the data type
(attribute 19)."""
warning_hysteresis: CIPAttribute[bytes] = CIPAttribute(20)
"""Recovery margin to clear a Warning condition, default 0 (attribute
20)."""
safe_state: CIPAttribute[int] = CIPAttribute(21, uint8)
"""Behavior for states other than Execute, default 0 (attribute 21)."""
safe_value: CIPAttribute[bytes] = CIPAttribute(22)
"""Value used for safe_state = Use Safe Value, default 0 (attribute
22)."""
subclass: CIPAttribute[int] = CIPAttribute(99, uint16)
"""0=No subclass (attribute 99)."""
[docs]
def read_value(self) -> int | float | bytes:
"""Read and decode ``value`` according to the current ``data_type``."""
return decode_elementary_value(self.value, self.data_type)
[docs]
def write_value(self, value: int | float | bytes) -> bytes:
"""Encode and write ``value`` according to the current
``data_type``."""
return self.set_empty(6, encode_elementary_value(value, self.data_type))
[docs]
class SSingleStageControllerObject(CIPObject):
"""Models a closed-loop controller (Setpoint / Process Variable /
Control Variable) in the Hierarchy of Semiconductor Equipment Devices
(See CIP Vol 1, §5-38): ``Process Variable = Setpoint``, achieved by
driving ``control_variable``.
``setpoint``/``process_variable`` (data_type-dependent) and
``control_variable`` (cv_data_type-dependent, Get-only) are exposed as
raw bytes with dedicated decode/encode helpers; ``alarm_error_band``/
``warning_error_band``/``safe_value`` share ``setpoint``'s data type
but are secondary and stay raw. This object has no object-specific
services.
"""
CLASS_CODE: ClassVar[ClassCode] = ClassCode.S_SINGLE_STAGE_CONTROLLER
number_of_attributes: CIPAttribute[int] = CIPAttribute(1, uint8)
"""Number of attributes supported in this object instance (attribute 1)."""
attribute_list: CIPAttribute[Collection[int]] = CIPAttribute(2, uint8[...])
"""List of attributes supported in this object instance (attribute 2)."""
data_type: CIPAttribute[int] = CIPAttribute(3, uint8)
"""Appendix C-6.1 code selecting the wire type of ``setpoint``/
``process_variable`` and related attributes (attribute 3)."""
data_units: CIPAttribute[bytes] = CIPAttribute(4)
"""ENGUNITS context of ``setpoint``/``process_variable`` (attribute 4, See
Appendix D)."""
control_mode: CIPAttribute[int] = CIPAttribute(5, uint8)
"""0=Normal [default], 1=Zero/Off/Closed, 2=Full/On/Open, 3=Hold, 4=Safe
State, 64-127=Device Specific, 128-255=Vendor Specific (attribute 5)."""
setpoint: CIPAttribute[bytes] = CIPAttribute(6)
"""The desired value for process_variable, default 0 (attribute 6,
required)."""
process_variable: CIPAttribute[bytes] = CIPAttribute(7)
"""The measured process parameter; required unless this device has no
internal sensor, default 0 (attribute 7)."""
cv_data_type: CIPAttribute[int] = CIPAttribute(8, uint8)
"""Appendix C-6.1 code selecting the wire type of ``control_variable``
(attribute 8)."""
control_variable: CIPAttribute[bytes] = CIPAttribute(9)
"""The controller's drive signal output; required unless this device has no
internal actuator, default 0 (attribute 9)."""
status: CIPAttribute[int] = CIPAttribute(10, uint8)
"""Bit 0=Alarm Exception, 1=Warning Exception, default 0 (attribute 10,
required)."""
alarm_enable: CIPAttribute[int] = CIPAttribute(11, uint8)
"""Enables setting the Alarm status bit (attribute 11)."""
warning_enable: CIPAttribute[int] = CIPAttribute(12, uint8)
"""Enables setting the Warning status bit (attribute 12)."""
alarm_settling_time: CIPAttribute[int] = CIPAttribute(13, uint16)
"""Milliseconds allowed for the control loop to settle within
alarm_error_band, default 0 (attribute 13)."""
alarm_error_band: CIPAttribute[bytes] = CIPAttribute(14)
"""Allowed Setpoint/Process Variable deviation before an Alarm timer
starts, default 0 (attribute 14)."""
warning_settling_time: CIPAttribute[int] = CIPAttribute(15, uint16)
"""Milliseconds allowed for the control loop to settle within
warning_error_band, default 0 (attribute 15)."""
warning_error_band: CIPAttribute[bytes] = CIPAttribute(16)
"""Allowed Setpoint/Process Variable deviation before a Warning timer
starts, default 0 (attribute 16)."""
safe_state: CIPAttribute[int] = CIPAttribute(17, uint8)
"""Behavior for states other than Executing, default 0 (attribute 17)."""
safe_value: CIPAttribute[bytes] = CIPAttribute(18)
"""Value used for safe_state = Use Safe Value, default 0 (attribute
18)."""
ramp_rate: CIPAttribute[int] = CIPAttribute(19, uint32)
"""Milliseconds to reach Setpoint; 0 = disabled [default] (attribute
19)."""
subclass: CIPAttribute[int] = CIPAttribute(99, uint16)
"""0=No subclass, 1=PID & Source Select, 2=DC Generator, 3=RF Generator,
4=Frequency Control (attribute 99)."""
[docs]
def read_setpoint(self) -> int | float | bytes:
"""Read and decode ``setpoint`` according to the current
``data_type``."""
return decode_elementary_value(self.setpoint, self.data_type)
[docs]
def write_setpoint(self, value: int | float | bytes) -> bytes:
"""Encode and write ``setpoint`` according to the current
``data_type``."""
return self.set_empty(6, encode_elementary_value(value, self.data_type))
[docs]
def read_process_variable(self) -> int | float | bytes:
"""Read and decode ``process_variable`` according to the current
``data_type``."""
return decode_elementary_value(self.process_variable, self.data_type)
[docs]
def write_process_variable(self, value: int | float | bytes) -> bytes:
"""Encode and write ``process_variable`` according to the current
``data_type``."""
return self.set_empty(7, encode_elementary_value(value, self.data_type))
[docs]
def read_control_variable(self) -> int | float | bytes:
"""Read and decode ``control_variable`` according to the current
``cv_data_type``."""
return decode_elementary_value(self.control_variable, self.cv_data_type)
[docs]
class SGasCalibrationObject(CIPObject):
"""Holds calibration parameters for one gas type of an associated S-Analog
Sensor object (selected via that object's ``calibration_object_instance``
attribute) (See CIP Vol 1, §5-39).
Instances typically add manufacturer specific calibration parameters
at attribute IDs > 100.
"""
CLASS_CODE: ClassVar[ClassCode] = ClassCode.S_GAS_CALIBRATION
number_of_attributes: CIPAttribute[int] = CIPAttribute(1, uint8)
"""Number of attributes supported (attribute 1)."""
attribute_list: CIPAttribute[Collection[int]] = CIPAttribute(2, uint8[...])
"""List of attributes supported by this object instance (attribute 2)."""
gas_standard_number: CIPAttribute[int] = CIPAttribute(3, uint16)
"""SEMI E52 Gas Type Number; 0 = no gas type specified [default]
(attribute 3, required)."""
valid_sensor_instance: CIPAttribute[int] = CIPAttribute(4, uint16)
"""S-Analog Sensor object instance ID this instance is valid for; 0 = none
[default] (attribute 4, required)."""
gas_symbol: CIPAttribute[str] = CIPAttribute(5, CIP_SHORT_STRING)
"""Gas type name, e.g. ``"N2"`` (attribute 5)."""
full_scale: CIPAttribute[FullScale] = CIPAttribute(6, FullScale)
"""Full Scale of the device using this instance, default (0, 0) (attribute
6)."""
additional_scaler: CIPAttribute[float] = CIPAttribute(7, float32)
"""Extra correction factor multiplied to the reading, e.g. to reuse this
calibration for a different gas; default 1.0 (attribute 7)."""
calibration_date: CIPAttribute[int] = CIPAttribute(8, uint16)
"""DATE this instance was last calibrated, default 0 (attribute 8)."""
calibration_gas_number: CIPAttribute[int] = CIPAttribute(9, uint16)
"""Gas number used to calibrate this instance, default 0 (attribute 9)."""
gas_correction_factor: CIPAttribute[float] = CIPAttribute(10, float32)
"""Simple correction factor alternative to a full algorithm, default 1.0
(attribute 10)."""
subclass: CIPAttribute[int] = CIPAttribute(99, uint16)
"""0=No subclass, 1=Standard T & P (attribute 99)."""
[docs]
def get_all_instances(self) -> Collection[GasCalibrationEntry]:
"""Invoke the class-level Get_All_Instances (0x4B): lists every
instance with its Gas Standard Number and valid sensor instance (See
§5-39.5)."""
response = self.message(0x4B, instance=0)
return unpack(GasCalibrationEntry[uint16::], response, order=LittleEndian)
[docs]
class TripPointObject(CIPObject):
"""Compares a Source input value to High/Low trip points and drives a
Destination output accordingly, e.g. to model a discrete alarm output
derived from an analog input (See CIP Vol 1, §5-40).
``high_trip_point``/``low_trip_point``/``hysteresis``/``input`` are all
"INT or based on Data Type attribute" and are exposed as raw bytes;
``input`` gets :meth:`read_input`/:meth:`write_input` decode helpers
since it is this object's primary measured value. ``source``/
``destination`` are bare (whole-payload) Packed EPATHs identifying the
attributes that feed Input and receive Output, respectively.
"""
CLASS_CODE: ClassVar[ClassCode] = ClassCode.TRIP_POINT
number_of_attributes: CIPAttribute[int] = CIPAttribute(1, uint8)
"""Number of attributes supported (attribute 1)."""
attribute_list: CIPAttribute[Collection[int]] = CIPAttribute(2, uint8[...])
"""List of attributes supported by this object instance (attribute 2)."""
high_trip_point: CIPAttribute[bytes] = CIPAttribute(3)
"""Value at or above which a trip condition occurs; at least one of
high_trip_point/low_trip_point is required, default 0 (attribute 3)."""
high_trip_enable: CIPAttribute[int] = CIPAttribute(4, uint8)
"""Enables the High Trip Point setting, default enabled (attribute 4)."""
low_trip_point: CIPAttribute[bytes] = CIPAttribute(5)
"""Value at or below which a trip condition occurs; at least one of
high_trip_point/low_trip_point is required, default 0 (attribute 5)."""
low_trip_enable: CIPAttribute[int] = CIPAttribute(6, uint8)
"""Enables the Low Trip Point setting, default enabled (attribute 6)."""
status: CIPAttribute[int] = CIPAttribute(7, uint8)
"""0=trip condition unasserted, 1=asserted (attribute 7, required)."""
polarity: CIPAttribute[int] = CIPAttribute(8, uint8)
"""0=Normal (Output = Status), 1=Reverse (Output = Status inverted)
(attribute 8)."""
override: CIPAttribute[int] = CIPAttribute(9, uint8)
"""0=Normal, 1=Force FALSE, 2=Force TRUE, 3=Freeze Status and Output; may
also be set internally by the device (attribute 9)."""
hysteresis: CIPAttribute[bytes] = CIPAttribute(10)
"""Recovery margin the Input must cross to clear a trip condition, same
data type as the trip points, default 0 (attribute 10)."""
delay: CIPAttribute[int] = CIPAttribute(11, uint16)
"""Milliseconds a trip condition must exist before being reported in
Status, default 0 (attribute 11)."""
destination: CIPAttribute[EPATH] = CIPAttribute(12, CIP_EPATH)
"""Path of the destination attribute that Output is written to (attribute
12, required)."""
output: CIPAttribute[int] = CIPAttribute(13, uint8)
"""Status as a function of Polarity (attribute 13, required)."""
source: CIPAttribute[EPATH] = CIPAttribute(14, CIP_EPATH)
"""Path of the source attribute that Input is read from (attribute 14,
required)."""
input: CIPAttribute[bytes] = CIPAttribute(15)
"""Value retrieved from Source and compared to the trip points (attribute
15, required)."""
data_units: CIPAttribute[bytes] = CIPAttribute(16)
"""ENGUNITS context of Input/trip points/Hysteresis, mirrors the source
attribute's units (attribute 16)."""
data_type: CIPAttribute[int] = CIPAttribute(17, uint8)
"""Appendix C-6.1 code for Input/trip points/Hysteresis; default = INT
(attribute 17)."""
subclass: CIPAttribute[int] = CIPAttribute(99, uint16)
"""0=No subclass (attribute 99)."""
[docs]
class SPartialPressureObject(CIPObject):
"""Scans the partial pressure of one configured AMU, AMU range or gas
species, e.g. for a mass spectrometer (See CIP Vol 1, §5-43).
Class attributes 32-39 (Filament Control/Status, Reading Invalid,
Emission Current, Multiplier On/Voltage, Samples, Scan Count) are only
available via :meth:`get_class_attribute`/:meth:`set_class_attribute`,
per this library's convention of never declaring typed descriptors for
class-level attributes.
"""
CLASS_CODE: ClassVar[ClassCode] = ClassCode.S_PARTIAL_PRESSURE
number_of_attributes: CIPAttribute[int] = CIPAttribute(1, uint8)
"""Number of attributes supported by this object instance (attribute 1)."""
attribute_list: CIPAttribute[Collection[int]] = CIPAttribute(2, uint8[...])
"""List of attributes supported by this object instance (attribute 2)."""
instance_type: CIPAttribute[int] = CIPAttribute(3, uint8)
"""0=AMU [default], 1=AMU Range, 2=Gas Species, 51-99=Device Specific,
100-255=Vendor Specific (attribute 3, required)."""
amu: CIPAttribute[float] = CIPAttribute(4, float32)
"""Atomic Mass Unit, or Starting AMU for an AMU Range instance, default 0
(attribute 4)."""
ending_amu: CIPAttribute[float] = CIPAttribute(5, float32)
"""Ending AMU for an AMU Range type instance (attribute 5)."""
gas_standard_number: CIPAttribute[int] = CIPAttribute(6, uint16)
"""SEMI E52 Gas Species Type Number; 0 = none specified [default]
(attribute 6)."""
status: CIPAttribute[int] = CIPAttribute(7, uint8)
"""Alarm and Warning state of this object instance (attribute 7,
required)."""
alarm_high_enable: CIPAttribute[int] = CIPAttribute(8, uint8)
"""Enables setting the Alarm High status bit, default disabled (attribute
8)."""
alarm_low_enable: CIPAttribute[int] = CIPAttribute(9, uint8)
"""Enables setting the Alarm Low status bit, default disabled (attribute
9)."""
warning_high_enable: CIPAttribute[int] = CIPAttribute(10, uint8)
"""Enables setting the Warning High status bit, default disabled (attribute
10)."""
warning_low_enable: CIPAttribute[int] = CIPAttribute(11, uint8)
"""Enables setting the Warning Low status bit, default disabled (attribute
11)."""
instance_enable: CIPAttribute[int] = CIPAttribute(12, uint8)
"""0=disable [default], 1=normal scan rate, 2=double scan rate,
51-99=Device Specific, 100-255=Vendor Specific (attribute 12, required)."""
partial_pressure: CIPAttribute[float] = CIPAttribute(13, float32)
"""The measured partial pressure of the specified AMU, in
``pressure_units`` (attribute 13, required)."""
pressure_units: CIPAttribute[bytes] = CIPAttribute(14)
"""ENGUNITS context of partial_pressure; default = Torr (attribute 14,
See Appendix D)."""
dwell_time: CIPAttribute[int] = CIPAttribute(15, uint16)
"""Milliseconds of acquisition time for this AMU; 0 = fastest possible
[default] (attribute 15)."""
electron_energy: CIPAttribute[float] = CIPAttribute(16, float32)
"""Electron Impact Ionization Voltage in eV, default factory configured
(attribute 16)."""
report_threshold: CIPAttribute[float] = CIPAttribute(17, float32)
"""Level above which partial_pressure is reported by Get_Pressures, default
0 (attribute 17)."""
alarm_trip_point_high: CIPAttribute[float] = CIPAttribute(18, float32)
"""Value above which an Alarm occurs, default = max for its data type
(attribute 18)."""
alarm_trip_point_low: CIPAttribute[float] = CIPAttribute(19, float32)
"""Value below which an Alarm occurs, default = min for its data type
(attribute 19)."""
alarm_hysteresis: CIPAttribute[float] = CIPAttribute(20, float32)
"""Recovery margin to clear an Alarm condition, default 0 (attribute
20)."""
warning_trip_point_high: CIPAttribute[float] = CIPAttribute(21, float32)
"""Value above which a Warning occurs, default = max for its data type
(attribute 21)."""
warning_trip_point_low: CIPAttribute[float] = CIPAttribute(22, float32)
"""Value below which a Warning occurs, default = min for its data type
(attribute 22)."""
warning_hysteresis: CIPAttribute[float] = CIPAttribute(23, float32)
"""Recovery margin to clear a Warning condition, default 0 (attribute
23)."""
group_id: CIPAttribute[int] = CIPAttribute(24, uint8)
"""Group (0-255) this instance belongs to, for Group_Enable (attribute
24)."""
subclass: CIPAttribute[int] = CIPAttribute(99, uint16)
"""0=No Subclass (attribute 99)."""
[docs]
def get_class_attribute(self, attribute: int) -> bytes:
"""Read an S-Partial Pressure class attribute from instance 0, e.g.
Filament Status (33) or Reading Invalid (34) (See §5-43.1)."""
return self.get_attr(attribute, instance=0)
[docs]
def set_class_attribute(self, attribute: int, value: bytes) -> bytes:
"""Write an S-Partial Pressure class attribute at instance 0, e.g.
Filament Control (32) (See §5-43.1)."""
return self.set_empty(attribute, value, instance=0)
[docs]
def create(self) -> bytes:
"""Invoke Create at the class level, allocating a new S-Partial
Pressure instance (See §5-43.3)."""
return self.message(CommonService.CREATE, instance=0)
[docs]
def delete_all(self) -> bytes:
"""Invoke Delete at the class level, removing every instance (See
§5-43.3)."""
return self._expect_empty(self.message(CommonService.DELETE, instance=0))
[docs]
def delete(self) -> bytes:
"""Invoke Delete on this instance (See §5-43.3)."""
return self._expect_empty(self.message(CommonService.DELETE))
[docs]
def create_range(
self,
start_amu: float,
end_amu: float,
num_instances: int,
starting_id: int = 0,
group_id: int = 0,
) -> CreateRangeResult:
"""Invoke the class-level Create_Range (0x4B): creates
``num_instances`` evenly spaced instances covering ``start_amu``
to ``end_amu``. ``starting_id`` = 0 uses the first available
contiguous ID range (See §5-43.4.1)."""
request = pack(
CreateRangeRequest(start_amu, end_amu, num_instances, starting_id, group_id),
CreateRangeRequest,
order=LittleEndian,
)
response = self.message(0x4B, request, instance=0)
return unpack(CreateRangeResult, response, order=LittleEndian)
[docs]
def get_instance_list(self) -> Collection[PartialPressureInstanceEntry]:
"""Invoke the class-level Get_Instance_List (0x4C): lists every enabled
instance with its AMU configuration (See §5-43.4.2)."""
response = self.message(0x4C, instance=0)
return unpack(
PartialPressureInstanceEntry[uint16::], response, order=LittleEndian
)
[docs]
def get_pressures(self) -> Collection[PartialPressureReading]:
"""Invoke the class-level Get_Pressures (0x4D): lists every enabled
Partial Pressure above its report_threshold (See §5-43.4.3)."""
response = self.message(0x4D, instance=0)
return unpack(PartialPressureReading[uint16::], response, order=LittleEndian)
[docs]
def get_all_pressures(self) -> Collection[PartialPressureReading]:
"""Invoke the class-level Get_All_Pressures (0x4E): lists every enabled
Partial Pressure, irrespective of report_threshold (See §5-43.4.4)."""
response = self.message(0x4E, instance=0)
return unpack(PartialPressureReading[uint16::], response, order=LittleEndian)
[docs]
def group_enable(self, enable: bool, group_id: int) -> bytes:
"""Invoke the class-level Group_Enable (0x4F): enables/disables every
instance whose ``group_id`` attribute matches (See §5-43.4.5)."""
request = pack(
GroupEnableRequest(1 if enable else 0, group_id),
GroupEnableRequest,
order=LittleEndian,
)
return self._expect_empty(self.message(0x4F, request, instance=0))
[docs]
class SSensorCalibrationObject(CIPObject):
"""Holds calibration parameters for one application of an associated
S-Analog Sensor object (selected via that object's
``calibration_object_instance`` attribute) (See CIP Vol 1, §5-44).
Attributes 16-31 are reserved for manufacturer defined calibration
coefficients and are not modeled individually; use :meth:`get`/
:meth:`set` with the desired attribute ID if a device implements
them.
"""
CLASS_CODE: ClassVar[ClassCode] = ClassCode.S_SENSOR_CALIBRATION
number_of_attributes: CIPAttribute[int] = CIPAttribute(1, uint8)
"""Number of attributes supported (attribute 1)."""
attribute_list: CIPAttribute[Collection[int]] = CIPAttribute(2, uint8[...])
"""List of attributes supported by this object instance (attribute 2)."""
calibration_id_number: CIPAttribute[int] = CIPAttribute(3, uint16)
"""Identifies the calibration, e.g. fluid type; default 0 (attribute 3,
required)."""
valid_sensor_instance: CIPAttribute[int] = CIPAttribute(4, uint16)
"""S-Analog Sensor object instance ID this instance is valid for; 0 = none
[default] (attribute 4, required)."""
calibration_name: CIPAttribute[str] = CIPAttribute(5, CIP_SHORT_STRING)
"""Representation of the calibration, e.g. material type, max 50 characters
(attribute 5)."""
full_scale: CIPAttribute[FullScale] = CIPAttribute(6, FullScale)
"""Full Scale of the device using this instance, default (0, 0) (attribute
6)."""
additional_scaler: CIPAttribute[float] = CIPAttribute(7, float32)
"""Extra correction factor multiplied to the reading, default 1.0
(attribute 7)."""
calibration_date: CIPAttribute[int] = CIPAttribute(8, uint16)
"""DATE this instance was last calibrated, default 0 (attribute 8)."""
temperature_data_units: CIPAttribute[bytes] = CIPAttribute(9)
"""ENGUNITS context of ``temperature`` (attribute 9)."""
temperature: CIPAttribute[float] = CIPAttribute(10, float32)
"""Temperature this calibration was made for/at (attribute 10)."""
pressure_data_units: CIPAttribute[bytes] = CIPAttribute(11)
"""ENGUNITS context of ``pressure`` (attribute 11)."""
pressure: CIPAttribute[float] = CIPAttribute(12, float32)
"""Pressure this calibration was made for/at (attribute 12)."""
coefficient_a: CIPAttribute[float] = CIPAttribute(13, float32)
"""The "a" term in the quadratic correction y = ax^2 + bx + c (attribute
13)."""
coefficient_b: CIPAttribute[float] = CIPAttribute(14, float32)
"""The "b" term in the quadratic correction y = ax^2 + bx + c (attribute
14)."""
coefficient_c: CIPAttribute[float] = CIPAttribute(15, float32)
"""The "c" term in the quadratic correction y = ax^2 + bx + c (attribute
15)."""
subclass: CIPAttribute[int] = CIPAttribute(99, uint16)
"""0=No subclass (attribute 99)."""
[docs]
def get_all_instances(self) -> Collection[SensorCalibrationEntry]:
"""Invoke the class-level Get_All_Instances (0x4B): lists every
instance with its Calibration ID Number and valid sensor instance (See
§5-44.7)."""
response = self.message(0x4B, instance=0)
return unpack(SensorCalibrationEntry[uint16::], response, order=LittleEndian)