oxy_lc.oxy_lc

  1import minimalmodbus
  2from oxy_lc.utilities.modbus_registers import HoldingRegister, InputRegister
  3from oxy_lc.utilities.conversions import twos_compliment
  4from enum import IntEnum
  5from time import sleep
  6
  7# region Errors
  8
  9class ValueRangeError(Exception):
 10    """Exception raised for value range error scenarios.
 11
 12    Attributes:
 13        message -- explanation of the error
 14    """
 15
 16    def __init__(self, message):
 17        self.message = message
 18        super().__init__(self.message)
 19
 20# endregion
 21
 22
 23class OxyLc(minimalmodbus.Instrument):
 24    """
 25    Main object for OxyLC communication
 26
 27    :param minimalmodbus: Child of minimalmodbus to use all included methods as standard
 28    """
 29
 30    def __init__(self, portname: str, slaveaddress: int = 1):
 31        """
 32        Initialiser for OxyLC communication. This should setup the connection to the device at the required protocol.
 33
 34        :param portname: Portname for the connection. Likely "COM*" on windows or "/dev/ttyUSB*" in Linux
 35        :type portname: str
 36        :param slaveaddress: Slave address of the device. Unless changed by the user this defaults to 1 on new products.
 37        :type slaveaddress: int
 38        """
 39        minimalmodbus.Instrument.__init__(self, portname, slaveaddress)
 40        self.serial.baudrate = 9600
 41
 42    # region enums
 43
 44    class StatusValues(IntEnum):
 45        IDLE = 0
 46        START_UP = 1
 47        OPERATING = 2
 48        SHUT_DOWN = 3
 49        STANDBY = 4
 50
 51    class SensorState(IntEnum):
 52        OFF = 0
 53        ON = 1
 54        STANDBY = 2
 55
 56    class HeaterOptions(IntEnum):
 57        HEATER_4V0 = 0
 58        HEATER_4V2 = 1
 59        HEATER_4V35 = 2
 60        HEATER_4V55 = 3
 61
 62    class SaveAndApply(IntEnum):
 63        IDLE = 0
 64        APPLY = 1
 65
 66    class CalibrationStatus(IntEnum):
 67        IDLE = 0
 68        IN_PROGRESS = 1
 69        COMPLETED = 2
 70
 71    class CalibrationControl(IntEnum):
 72        DEFAULT = 0
 73        ACTIVATE = 1
 74        RESET = 2
 75
 76    class BaudRates(IntEnum):
 77        _2400 = 0
 78        _4800 = 1
 79        _9600 = 2
 80        _19200 = 3
 81        _38400 = 4
 82        _57600 = 5
 83        _115200 = 6
 84
 85    class Parity(IntEnum):
 86        NONE = 0
 87        ODD = 1
 88        EVEN = 2
 89
 90    class StopBits(IntEnum):
 91        _1 = 0
 92        _2 = 1
 93
 94    class RS485ApplyChanges(IntEnum):
 95        IDLE = 0
 96        APPLY = 1
 97
 98    # endregion
 99
100    # region Properties
101
102    @property
103    def o2_average(self) -> float:
104        """
105        Get live sensor reading - O2 Average
106
107        :return: Current averaged O2 reading from the sensor (%)
108        :rtype: float
109        """
110        o2_reading = self.read_register(InputRegister.O2_AVERAGE, functioncode=4)
111        return o2_reading / 100
112
113    @property
114    def o2_raw(self) -> float:
115        """
116        Get live sensor reading - O2 raw
117
118        :return: Current raw O2 reading from the sensor (%)
119        :rtype: float
120        """
121        o2_reading = self.read_register(InputRegister.O2_RAW, functioncode=4)
122        return o2_reading / 100
123
124    @property
125    def asymmetry(self) -> float:
126        """
127        Get live sensor reading - O2 asymmetry
128
129        :return: Current asymmetry of the sensor
130        :rtype: float
131        """
132        asymmetry_reading = self.read_register(InputRegister.ASYMMETRY, functioncode=4)
133        return asymmetry_reading / 1000
134
135    @property
136    def status(self) -> StatusValues:
137        """
138        Get live sensor reading - current device status
139        Returned as Enum from StatusValues
140
141        :return: Current status of the device
142        :rtype: StatusValues
143        """
144        status = self.read_register(InputRegister.SYSTEM_STATUS, functioncode=4)
145        return self.StatusValues(status)
146
147    @property
148    def sensor_state(self) -> SensorState:
149        """
150        Get live sensor reading - Sensor State
151
152        :return: Current state of the sensor
153        :rtype: SensorState
154        """
155        _sensor_state = self.read_register(HoldingRegister.SENSOR_STATE, functioncode=3)
156        return self.SensorState(_sensor_state)
157
158    @sensor_state.setter
159    def sensor_state(self, state: SensorState) -> None:
160        """
161        Set the current state of the sensor
162
163        :param state: State option from Enum SensorState
164        :type state: SensorState
165        """
166        self.write_register(HoldingRegister.SENSOR_STATE, state)
167
168    @property
169    def heater_voltage(self) -> float:
170        """
171        Get sensor setting - heater voltage
172
173        :return: Current set heater voltage of the sensor (Volts)
174        :rtype: float
175        """
176        _heater_Voltage = self.read_register(
177            InputRegister.HEATER_VOLTAGE, functioncode=4
178        )
179        return _heater_Voltage / 100
180
181    @heater_voltage.setter
182    def heater_voltage(self, set_voltage: HeaterOptions) -> None:
183        """
184        Set the current heater voltage for the sensor
185
186        :param state: State option from Enum HeaterOptions
187        :type state: HeaterOptions
188        """
189        self.write_register(HoldingRegister.HEATER_VOLTAGE, set_voltage)
190
191    @property
192    def warnings(self) -> str:
193        """
194        Getting current warnings as a string of bits as received by the sensor
195
196        :return: Warning states as bit string
197        :rtype: str
198        """
199
200        warnings_hex = self.read_register(InputRegister.WARNINGS, functioncode=4)
201        
202        decode_to_bits = f"{warnings_hex:08b}"
203
204        return decode_to_bits
205
206    @property
207    def td_average(self) -> float:
208        """
209        Get live sensor TD average
210
211        :return: TD Average (ms)
212        :rtype: float
213        """
214        td_average = self.read_register(InputRegister.TD_AVERAGE, functioncode=4)
215        return td_average / 10
216
217    @property
218    def td_raw(self) -> float:
219        """
220        Get live sensor TD raw
221
222        :return: TD raw (ms)
223        :rtype: float
224        """
225        td_raw = self.read_register(InputRegister.TD_RAW, functioncode=4)
226        return td_raw / 10
227
228    @property
229    def tp(self) -> float:
230        """
231        Get live sensor TP
232
233        :return: TP (ms)
234        :rtype: float
235        """
236        tp = self.read_register(InputRegister.TP, functioncode=4)
237        return tp / 10
238
239    @property
240    def t1(self) -> float:
241        """
242        Get live sensor T1
243
244        :return: T1 (ms)
245        :rtype: float
246        """
247        t1 = self.read_register(InputRegister.T1, functioncode=4)
248        return t1 / 10
249
250    @property
251    def t2(self) -> float:
252        """
253        Get live sensor T2
254
255        :return: T2 (ms)
256        :rtype: float
257        """
258        t2 = self.read_register(InputRegister.T2, functioncode=4)
259        return t2 / 10
260
261    @property
262    def t4(self) -> float:
263        """
264        Get live sensor T4
265
266        :return: T4 (ms)
267        :rtype: float
268        """
269        t4 = self.read_register(InputRegister.T4, functioncode=4)
270        return t4 / 10
271
272    @property
273    def t5(self) -> float:
274        """
275        Get live sensor T5
276
277        :return: T5 (ms)
278        :rtype: float
279        """
280        t5 = self.read_register(InputRegister.T5, functioncode=4)
281        return t5 / 10
282
283    @property
284    def pp_o2_real(self) -> float:
285        """
286        Get live ppO2 Real
287
288        :return: ppO2 Real
289        :rtype: float
290        """
291        pp_o2 = self.read_register(InputRegister.PPO2_REAL, functioncode=4)
292        return pp_o2 / 10
293
294    @property
295    def pp_o2_raw(self) -> float:
296        """
297        Get live ppO2 raw
298
299        :return: ppO2 raw
300        :rtype: float
301        """
302        pp_o2 = self.read_register(InputRegister.PPO2_RAW, functioncode=4)
303        return pp_o2 / 10
304
305    @property
306    def pressure(self) -> float:
307        """
308        Get live pressure
309
310        :return: pressure (mbar)
311        :rtype: float
312        """
313        _pressure = self.read_register(InputRegister.PRESSURE, functioncode=4)
314        return _pressure
315
316    @property
317    def pressure_sens_temperature(self) -> float:
318        """
319        Get live temperature of the pressure sensor
320
321        :return: temperature (°C)
322        :rtype: float
323        """
324        temp_decimal = self.read_register(
325            InputRegister.PRESSURE_SENS_TEMP, functioncode=4
326        )
327
328        converted_temperature = twos_compliment(temp_decimal, 16)
329
330        return converted_temperature
331
332    @property
333    def calibration_status(self) -> CalibrationStatus:
334        """
335        Get current calibration status of the sensor
336
337        :return: calibration status
338        :rtype: CalibrationStatus
339        """
340        _cal_status = self.read_register(
341            InputRegister.CALIBRATION_STATUS, functioncode=4
342        )
343        return self.CalibrationStatus(_cal_status)
344
345    @property
346    def year_of_manufacture(self) -> int:
347        """
348        Get Year of Manufacture
349
350        :return: Year of Manufacture (YYYY)
351        :rtype: int
352        """
353        _year_of_manufacture = self.read_register(InputRegister.YOM, functioncode=4)
354        return _year_of_manufacture
355
356    @property
357    def day_of_manufacture(self) -> int:
358        """
359        Get Day of Manufacture
360
361        :return: Day of Manufacture (DDD)
362        :rtype: int
363        """
364        _day_of_manufacture = self.read_register(InputRegister.DOM, functioncode=4)
365        return _day_of_manufacture
366
367    @property
368    def serial_number(self) -> int:
369        """
370        Get Serial Number
371
372        :return: Serial Number (XXXXX)
373        :rtype: int
374        """
375        _serial_number = self.read_register(InputRegister.SERIAL_NO, functioncode=4)
376        return _serial_number
377
378    @property
379    def software_revision(self) -> int:
380        """
381        Get Software Revision
382
383        :return: Software Revision (RRR)
384        :rtype: int
385        """
386        _software_revision = self.read_register(
387            InputRegister.SOFTWARE_REV, functioncode=4
388        )
389        return _software_revision
390
391    @property
392    def calibration_value(self) -> float:
393        """
394        Return current calibration value
395        calibration_value defaults to 20.7 on new interface boards.
396
397        :return: calibration (%)
398        :rtype: float
399        """
400        _calibration_value = self.read_register(HoldingRegister.CALIBRATION_PERCENT)
401        return _calibration_value / 100
402
403    @calibration_value.setter
404    def calibration_value(self, value: float) -> None:
405        """
406        Set the calibration value for the sensor.
407        Value is then stored in memory even after power cycle
408
409        :param value: Calibration value between 0% and 100%
410        :type value: float
411        :raises self.ValueRangeError: Value out of range
412        """
413        if (value < 0) or (value > 100):
414            raise ValueRangeError(
415                f"Calibration value {value} out of range. Value Must be between 0 and 100"
416            )
417
418        self.write_register(HoldingRegister.CALIBRATION_PERCENT, int(value * 100))
419
420    @property
421    def calibration_control(self) -> CalibrationControl:
422        """
423        Current Calibration control setting
424
425        :return: calibration control
426        :rtype: CalibrationControl
427        """
428        _calibration_control = self.read_register(HoldingRegister.CALIBRATION_CONTROL)
429        return self.CalibrationControl(_calibration_control)
430
431    @calibration_control.setter
432    def calibration_control(self, control_setting: CalibrationControl) -> None:
433        """
434        Set calibration control
435
436        :param control_setting: Control setting
437        :type control_setting: CalibrationControl
438        """
439        self.write_register(HoldingRegister.CALIBRATION_CONTROL, control_setting)
440
441    @property
442    def device_address(self) -> int:
443        """
444        Get device address
445
446        :return: Address (1-247)
447        :rtype: int
448        """
449        _address = self.read_register(HoldingRegister.ADDRESS)
450        return _address
451
452    @device_address.setter
453    def device_address(self, new_address: int) -> None:
454        """
455        Set new device Address
456
457        :param new_address: between 1 & 247
458        :type new_address: int
459        :raises ValueRangeError: Address out of range
460        """
461        if (new_address < 1) or (new_address > 247):
462            raise ValueRangeError("Address must be between 1 and 247")
463
464        self.write_register(HoldingRegister.ADDRESS, new_address)
465
466    @property
467    def parity(self) -> Parity:
468        """
469        Get device parity
470
471        :return: parity
472        :rtype: Parity
473        """
474        _parity = self.read_register(HoldingRegister.PARITY)
475        return self.Parity(_parity)
476
477    @parity.setter
478    def parity(self, new_parity: Parity) -> None:
479        """
480        Set new device parity
481
482        :param new_parity: new parity from enum
483        :type new_parity: Parity
484        """
485        self.write_register(HoldingRegister.PARITY, new_parity)
486
487    # endregion
488
489    # region Methods
490
491    def turn_on(self):
492        """
493        Turn the sensor On
494        """
495        self.sensor_state = self.SensorState.ON
496
497    def turn_off(self):
498        """
499        Turn the sensor Off
500        """
501        self.sensor_state = self.SensorState.OFF
502
503    def calibrate(self, calibration_value: float | None = None) -> bool:
504        """
505        Calibrate the sensor to the given percent value
506
507        :param calibration_precent: O2 percent for calibration, If None is passed it will use the stored calibration_value.
508                                    calibration_value defaults to 20.7 on new interface boards.
509        :type calibration_precent: float, optional
510        :return: boolean indication of success
511        :rtype: bool
512        """
513        if self.status != self.StatusValues.OPERATING:
514            print("Sensor must be in operation to calibrate")
515            return False
516
517        if calibration_value:
518            try:
519                self.calibration_value = calibration_value
520            except ValueRangeError as e:
521                print(e)
522                return False
523
524        self.calibration_control = self.CalibrationControl.ACTIVATE
525
526        count = 0
527        timeout = 5
528        while (self.calibration_status != self.CalibrationStatus.COMPLETED) and (
529            count != timeout
530        ):
531            sleep(1)
532            count += 1
533
534        self.write_register(
535            HoldingRegister.CALIBRATION_CONTROL, self.CalibrationControl.RESET
536        )
537
538        if count == timeout:
539            print("calibration timeout")
540            return False
541        else:
542            return True
543
544    def clear_error_flags(self) -> None:
545        """
546        Clears error flags on the device
547        """
548        self.write_register(HoldingRegister.CLEAR_FLAGS, 1)
549
550    def set_and_apply_heater_voltage(self, set_voltage: HeaterOptions | None) -> None:
551        """
552        Set the current heater voltage for the sensor
553
554        :param state: State option from Enum HeaterOptions
555        :type state: HeaterOptions
556        """
557        if set_voltage:
558            self.heater_voltage = set_voltage
559
560        # MinimalModbus will throw a NoResonseError when this register is written.
561        # It must be passed or it will crash the program
562        try:
563            self.write_register(
564                HoldingRegister.HEATER_VOLTAGE_SAVE, self.SaveAndApply.APPLY
565            )
566        except minimalmodbus.NoResponseError:
567            sleep(2)
568
569        self.sensor_state = self.SensorState.ON
570
571
572    def display_warnings(self) -> dict[str:bool]:
573        """
574        Reads and decodes the warnings and returns a dictionary with each warning/error with a corresponding boolean showing error state
575
576        :return: Warning states for each of the representative bits (True is error)
577        :rtype: dict[str: bool]
578        """
579        warning_states = {
580            "Pump Error": False,
581            "Heater Voltage Error": False,
582            "Asymmetry Warning": False,
583            "O2 Low Warning": False,
584            "Pressure Sensor Warning": False,
585            "Pressure Sensor Error": False,
586        }
587        warnings_bits = self.warnings
588
589        for count, state in enumerate(warning_states):
590            if warnings_bits[::-1][count] == '1':
591                warning_states[state] = True
592
593        return warning_states
594
595    # endregion
class ValueRangeError(builtins.Exception):
10class ValueRangeError(Exception):
11    """Exception raised for value range error scenarios.
12
13    Attributes:
14        message -- explanation of the error
15    """
16
17    def __init__(self, message):
18        self.message = message
19        super().__init__(self.message)

Exception raised for value range error scenarios.

Attributes: message -- explanation of the error

ValueRangeError(message)
17    def __init__(self, message):
18        self.message = message
19        super().__init__(self.message)
message
class OxyLc(minimalmodbus.Instrument):
 24class OxyLc(minimalmodbus.Instrument):
 25    """
 26    Main object for OxyLC communication
 27
 28    :param minimalmodbus: Child of minimalmodbus to use all included methods as standard
 29    """
 30
 31    def __init__(self, portname: str, slaveaddress: int = 1):
 32        """
 33        Initialiser for OxyLC communication. This should setup the connection to the device at the required protocol.
 34
 35        :param portname: Portname for the connection. Likely "COM*" on windows or "/dev/ttyUSB*" in Linux
 36        :type portname: str
 37        :param slaveaddress: Slave address of the device. Unless changed by the user this defaults to 1 on new products.
 38        :type slaveaddress: int
 39        """
 40        minimalmodbus.Instrument.__init__(self, portname, slaveaddress)
 41        self.serial.baudrate = 9600
 42
 43    # region enums
 44
 45    class StatusValues(IntEnum):
 46        IDLE = 0
 47        START_UP = 1
 48        OPERATING = 2
 49        SHUT_DOWN = 3
 50        STANDBY = 4
 51
 52    class SensorState(IntEnum):
 53        OFF = 0
 54        ON = 1
 55        STANDBY = 2
 56
 57    class HeaterOptions(IntEnum):
 58        HEATER_4V0 = 0
 59        HEATER_4V2 = 1
 60        HEATER_4V35 = 2
 61        HEATER_4V55 = 3
 62
 63    class SaveAndApply(IntEnum):
 64        IDLE = 0
 65        APPLY = 1
 66
 67    class CalibrationStatus(IntEnum):
 68        IDLE = 0
 69        IN_PROGRESS = 1
 70        COMPLETED = 2
 71
 72    class CalibrationControl(IntEnum):
 73        DEFAULT = 0
 74        ACTIVATE = 1
 75        RESET = 2
 76
 77    class BaudRates(IntEnum):
 78        _2400 = 0
 79        _4800 = 1
 80        _9600 = 2
 81        _19200 = 3
 82        _38400 = 4
 83        _57600 = 5
 84        _115200 = 6
 85
 86    class Parity(IntEnum):
 87        NONE = 0
 88        ODD = 1
 89        EVEN = 2
 90
 91    class StopBits(IntEnum):
 92        _1 = 0
 93        _2 = 1
 94
 95    class RS485ApplyChanges(IntEnum):
 96        IDLE = 0
 97        APPLY = 1
 98
 99    # endregion
100
101    # region Properties
102
103    @property
104    def o2_average(self) -> float:
105        """
106        Get live sensor reading - O2 Average
107
108        :return: Current averaged O2 reading from the sensor (%)
109        :rtype: float
110        """
111        o2_reading = self.read_register(InputRegister.O2_AVERAGE, functioncode=4)
112        return o2_reading / 100
113
114    @property
115    def o2_raw(self) -> float:
116        """
117        Get live sensor reading - O2 raw
118
119        :return: Current raw O2 reading from the sensor (%)
120        :rtype: float
121        """
122        o2_reading = self.read_register(InputRegister.O2_RAW, functioncode=4)
123        return o2_reading / 100
124
125    @property
126    def asymmetry(self) -> float:
127        """
128        Get live sensor reading - O2 asymmetry
129
130        :return: Current asymmetry of the sensor
131        :rtype: float
132        """
133        asymmetry_reading = self.read_register(InputRegister.ASYMMETRY, functioncode=4)
134        return asymmetry_reading / 1000
135
136    @property
137    def status(self) -> StatusValues:
138        """
139        Get live sensor reading - current device status
140        Returned as Enum from StatusValues
141
142        :return: Current status of the device
143        :rtype: StatusValues
144        """
145        status = self.read_register(InputRegister.SYSTEM_STATUS, functioncode=4)
146        return self.StatusValues(status)
147
148    @property
149    def sensor_state(self) -> SensorState:
150        """
151        Get live sensor reading - Sensor State
152
153        :return: Current state of the sensor
154        :rtype: SensorState
155        """
156        _sensor_state = self.read_register(HoldingRegister.SENSOR_STATE, functioncode=3)
157        return self.SensorState(_sensor_state)
158
159    @sensor_state.setter
160    def sensor_state(self, state: SensorState) -> None:
161        """
162        Set the current state of the sensor
163
164        :param state: State option from Enum SensorState
165        :type state: SensorState
166        """
167        self.write_register(HoldingRegister.SENSOR_STATE, state)
168
169    @property
170    def heater_voltage(self) -> float:
171        """
172        Get sensor setting - heater voltage
173
174        :return: Current set heater voltage of the sensor (Volts)
175        :rtype: float
176        """
177        _heater_Voltage = self.read_register(
178            InputRegister.HEATER_VOLTAGE, functioncode=4
179        )
180        return _heater_Voltage / 100
181
182    @heater_voltage.setter
183    def heater_voltage(self, set_voltage: HeaterOptions) -> None:
184        """
185        Set the current heater voltage for the sensor
186
187        :param state: State option from Enum HeaterOptions
188        :type state: HeaterOptions
189        """
190        self.write_register(HoldingRegister.HEATER_VOLTAGE, set_voltage)
191
192    @property
193    def warnings(self) -> str:
194        """
195        Getting current warnings as a string of bits as received by the sensor
196
197        :return: Warning states as bit string
198        :rtype: str
199        """
200
201        warnings_hex = self.read_register(InputRegister.WARNINGS, functioncode=4)
202        
203        decode_to_bits = f"{warnings_hex:08b}"
204
205        return decode_to_bits
206
207    @property
208    def td_average(self) -> float:
209        """
210        Get live sensor TD average
211
212        :return: TD Average (ms)
213        :rtype: float
214        """
215        td_average = self.read_register(InputRegister.TD_AVERAGE, functioncode=4)
216        return td_average / 10
217
218    @property
219    def td_raw(self) -> float:
220        """
221        Get live sensor TD raw
222
223        :return: TD raw (ms)
224        :rtype: float
225        """
226        td_raw = self.read_register(InputRegister.TD_RAW, functioncode=4)
227        return td_raw / 10
228
229    @property
230    def tp(self) -> float:
231        """
232        Get live sensor TP
233
234        :return: TP (ms)
235        :rtype: float
236        """
237        tp = self.read_register(InputRegister.TP, functioncode=4)
238        return tp / 10
239
240    @property
241    def t1(self) -> float:
242        """
243        Get live sensor T1
244
245        :return: T1 (ms)
246        :rtype: float
247        """
248        t1 = self.read_register(InputRegister.T1, functioncode=4)
249        return t1 / 10
250
251    @property
252    def t2(self) -> float:
253        """
254        Get live sensor T2
255
256        :return: T2 (ms)
257        :rtype: float
258        """
259        t2 = self.read_register(InputRegister.T2, functioncode=4)
260        return t2 / 10
261
262    @property
263    def t4(self) -> float:
264        """
265        Get live sensor T4
266
267        :return: T4 (ms)
268        :rtype: float
269        """
270        t4 = self.read_register(InputRegister.T4, functioncode=4)
271        return t4 / 10
272
273    @property
274    def t5(self) -> float:
275        """
276        Get live sensor T5
277
278        :return: T5 (ms)
279        :rtype: float
280        """
281        t5 = self.read_register(InputRegister.T5, functioncode=4)
282        return t5 / 10
283
284    @property
285    def pp_o2_real(self) -> float:
286        """
287        Get live ppO2 Real
288
289        :return: ppO2 Real
290        :rtype: float
291        """
292        pp_o2 = self.read_register(InputRegister.PPO2_REAL, functioncode=4)
293        return pp_o2 / 10
294
295    @property
296    def pp_o2_raw(self) -> float:
297        """
298        Get live ppO2 raw
299
300        :return: ppO2 raw
301        :rtype: float
302        """
303        pp_o2 = self.read_register(InputRegister.PPO2_RAW, functioncode=4)
304        return pp_o2 / 10
305
306    @property
307    def pressure(self) -> float:
308        """
309        Get live pressure
310
311        :return: pressure (mbar)
312        :rtype: float
313        """
314        _pressure = self.read_register(InputRegister.PRESSURE, functioncode=4)
315        return _pressure
316
317    @property
318    def pressure_sens_temperature(self) -> float:
319        """
320        Get live temperature of the pressure sensor
321
322        :return: temperature (°C)
323        :rtype: float
324        """
325        temp_decimal = self.read_register(
326            InputRegister.PRESSURE_SENS_TEMP, functioncode=4
327        )
328
329        converted_temperature = twos_compliment(temp_decimal, 16)
330
331        return converted_temperature
332
333    @property
334    def calibration_status(self) -> CalibrationStatus:
335        """
336        Get current calibration status of the sensor
337
338        :return: calibration status
339        :rtype: CalibrationStatus
340        """
341        _cal_status = self.read_register(
342            InputRegister.CALIBRATION_STATUS, functioncode=4
343        )
344        return self.CalibrationStatus(_cal_status)
345
346    @property
347    def year_of_manufacture(self) -> int:
348        """
349        Get Year of Manufacture
350
351        :return: Year of Manufacture (YYYY)
352        :rtype: int
353        """
354        _year_of_manufacture = self.read_register(InputRegister.YOM, functioncode=4)
355        return _year_of_manufacture
356
357    @property
358    def day_of_manufacture(self) -> int:
359        """
360        Get Day of Manufacture
361
362        :return: Day of Manufacture (DDD)
363        :rtype: int
364        """
365        _day_of_manufacture = self.read_register(InputRegister.DOM, functioncode=4)
366        return _day_of_manufacture
367
368    @property
369    def serial_number(self) -> int:
370        """
371        Get Serial Number
372
373        :return: Serial Number (XXXXX)
374        :rtype: int
375        """
376        _serial_number = self.read_register(InputRegister.SERIAL_NO, functioncode=4)
377        return _serial_number
378
379    @property
380    def software_revision(self) -> int:
381        """
382        Get Software Revision
383
384        :return: Software Revision (RRR)
385        :rtype: int
386        """
387        _software_revision = self.read_register(
388            InputRegister.SOFTWARE_REV, functioncode=4
389        )
390        return _software_revision
391
392    @property
393    def calibration_value(self) -> float:
394        """
395        Return current calibration value
396        calibration_value defaults to 20.7 on new interface boards.
397
398        :return: calibration (%)
399        :rtype: float
400        """
401        _calibration_value = self.read_register(HoldingRegister.CALIBRATION_PERCENT)
402        return _calibration_value / 100
403
404    @calibration_value.setter
405    def calibration_value(self, value: float) -> None:
406        """
407        Set the calibration value for the sensor.
408        Value is then stored in memory even after power cycle
409
410        :param value: Calibration value between 0% and 100%
411        :type value: float
412        :raises self.ValueRangeError: Value out of range
413        """
414        if (value < 0) or (value > 100):
415            raise ValueRangeError(
416                f"Calibration value {value} out of range. Value Must be between 0 and 100"
417            )
418
419        self.write_register(HoldingRegister.CALIBRATION_PERCENT, int(value * 100))
420
421    @property
422    def calibration_control(self) -> CalibrationControl:
423        """
424        Current Calibration control setting
425
426        :return: calibration control
427        :rtype: CalibrationControl
428        """
429        _calibration_control = self.read_register(HoldingRegister.CALIBRATION_CONTROL)
430        return self.CalibrationControl(_calibration_control)
431
432    @calibration_control.setter
433    def calibration_control(self, control_setting: CalibrationControl) -> None:
434        """
435        Set calibration control
436
437        :param control_setting: Control setting
438        :type control_setting: CalibrationControl
439        """
440        self.write_register(HoldingRegister.CALIBRATION_CONTROL, control_setting)
441
442    @property
443    def device_address(self) -> int:
444        """
445        Get device address
446
447        :return: Address (1-247)
448        :rtype: int
449        """
450        _address = self.read_register(HoldingRegister.ADDRESS)
451        return _address
452
453    @device_address.setter
454    def device_address(self, new_address: int) -> None:
455        """
456        Set new device Address
457
458        :param new_address: between 1 & 247
459        :type new_address: int
460        :raises ValueRangeError: Address out of range
461        """
462        if (new_address < 1) or (new_address > 247):
463            raise ValueRangeError("Address must be between 1 and 247")
464
465        self.write_register(HoldingRegister.ADDRESS, new_address)
466
467    @property
468    def parity(self) -> Parity:
469        """
470        Get device parity
471
472        :return: parity
473        :rtype: Parity
474        """
475        _parity = self.read_register(HoldingRegister.PARITY)
476        return self.Parity(_parity)
477
478    @parity.setter
479    def parity(self, new_parity: Parity) -> None:
480        """
481        Set new device parity
482
483        :param new_parity: new parity from enum
484        :type new_parity: Parity
485        """
486        self.write_register(HoldingRegister.PARITY, new_parity)
487
488    # endregion
489
490    # region Methods
491
492    def turn_on(self):
493        """
494        Turn the sensor On
495        """
496        self.sensor_state = self.SensorState.ON
497
498    def turn_off(self):
499        """
500        Turn the sensor Off
501        """
502        self.sensor_state = self.SensorState.OFF
503
504    def calibrate(self, calibration_value: float | None = None) -> bool:
505        """
506        Calibrate the sensor to the given percent value
507
508        :param calibration_precent: O2 percent for calibration, If None is passed it will use the stored calibration_value.
509                                    calibration_value defaults to 20.7 on new interface boards.
510        :type calibration_precent: float, optional
511        :return: boolean indication of success
512        :rtype: bool
513        """
514        if self.status != self.StatusValues.OPERATING:
515            print("Sensor must be in operation to calibrate")
516            return False
517
518        if calibration_value:
519            try:
520                self.calibration_value = calibration_value
521            except ValueRangeError as e:
522                print(e)
523                return False
524
525        self.calibration_control = self.CalibrationControl.ACTIVATE
526
527        count = 0
528        timeout = 5
529        while (self.calibration_status != self.CalibrationStatus.COMPLETED) and (
530            count != timeout
531        ):
532            sleep(1)
533            count += 1
534
535        self.write_register(
536            HoldingRegister.CALIBRATION_CONTROL, self.CalibrationControl.RESET
537        )
538
539        if count == timeout:
540            print("calibration timeout")
541            return False
542        else:
543            return True
544
545    def clear_error_flags(self) -> None:
546        """
547        Clears error flags on the device
548        """
549        self.write_register(HoldingRegister.CLEAR_FLAGS, 1)
550
551    def set_and_apply_heater_voltage(self, set_voltage: HeaterOptions | None) -> None:
552        """
553        Set the current heater voltage for the sensor
554
555        :param state: State option from Enum HeaterOptions
556        :type state: HeaterOptions
557        """
558        if set_voltage:
559            self.heater_voltage = set_voltage
560
561        # MinimalModbus will throw a NoResonseError when this register is written.
562        # It must be passed or it will crash the program
563        try:
564            self.write_register(
565                HoldingRegister.HEATER_VOLTAGE_SAVE, self.SaveAndApply.APPLY
566            )
567        except minimalmodbus.NoResponseError:
568            sleep(2)
569
570        self.sensor_state = self.SensorState.ON
571
572
573    def display_warnings(self) -> dict[str:bool]:
574        """
575        Reads and decodes the warnings and returns a dictionary with each warning/error with a corresponding boolean showing error state
576
577        :return: Warning states for each of the representative bits (True is error)
578        :rtype: dict[str: bool]
579        """
580        warning_states = {
581            "Pump Error": False,
582            "Heater Voltage Error": False,
583            "Asymmetry Warning": False,
584            "O2 Low Warning": False,
585            "Pressure Sensor Warning": False,
586            "Pressure Sensor Error": False,
587        }
588        warnings_bits = self.warnings
589
590        for count, state in enumerate(warning_states):
591            if warnings_bits[::-1][count] == '1':
592                warning_states[state] = True
593
594        return warning_states
595
596    # endregion

Main object for OxyLC communication

Parameters
  • minimalmodbus: Child of minimalmodbus to use all included methods as standard
OxyLc(portname: str, slaveaddress: int = 1)
31    def __init__(self, portname: str, slaveaddress: int = 1):
32        """
33        Initialiser for OxyLC communication. This should setup the connection to the device at the required protocol.
34
35        :param portname: Portname for the connection. Likely "COM*" on windows or "/dev/ttyUSB*" in Linux
36        :type portname: str
37        :param slaveaddress: Slave address of the device. Unless changed by the user this defaults to 1 on new products.
38        :type slaveaddress: int
39        """
40        minimalmodbus.Instrument.__init__(self, portname, slaveaddress)
41        self.serial.baudrate = 9600

Initialiser for OxyLC communication. This should setup the connection to the device at the required protocol.

Parameters
  • portname: Portname for the connection. Likely "COM" on windows or "/dev/ttyUSB" in Linux
  • slaveaddress: Slave address of the device. Unless changed by the user this defaults to 1 on new products.
o2_average: float
103    @property
104    def o2_average(self) -> float:
105        """
106        Get live sensor reading - O2 Average
107
108        :return: Current averaged O2 reading from the sensor (%)
109        :rtype: float
110        """
111        o2_reading = self.read_register(InputRegister.O2_AVERAGE, functioncode=4)
112        return o2_reading / 100

Get live sensor reading - O2 Average

Returns

Current averaged O2 reading from the sensor (%)

o2_raw: float
114    @property
115    def o2_raw(self) -> float:
116        """
117        Get live sensor reading - O2 raw
118
119        :return: Current raw O2 reading from the sensor (%)
120        :rtype: float
121        """
122        o2_reading = self.read_register(InputRegister.O2_RAW, functioncode=4)
123        return o2_reading / 100

Get live sensor reading - O2 raw

Returns

Current raw O2 reading from the sensor (%)

asymmetry: float
125    @property
126    def asymmetry(self) -> float:
127        """
128        Get live sensor reading - O2 asymmetry
129
130        :return: Current asymmetry of the sensor
131        :rtype: float
132        """
133        asymmetry_reading = self.read_register(InputRegister.ASYMMETRY, functioncode=4)
134        return asymmetry_reading / 1000

Get live sensor reading - O2 asymmetry

Returns

Current asymmetry of the sensor

status: OxyLc.StatusValues
136    @property
137    def status(self) -> StatusValues:
138        """
139        Get live sensor reading - current device status
140        Returned as Enum from StatusValues
141
142        :return: Current status of the device
143        :rtype: StatusValues
144        """
145        status = self.read_register(InputRegister.SYSTEM_STATUS, functioncode=4)
146        return self.StatusValues(status)

Get live sensor reading - current device status Returned as Enum from StatusValues

Returns

Current status of the device

sensor_state: OxyLc.SensorState
148    @property
149    def sensor_state(self) -> SensorState:
150        """
151        Get live sensor reading - Sensor State
152
153        :return: Current state of the sensor
154        :rtype: SensorState
155        """
156        _sensor_state = self.read_register(HoldingRegister.SENSOR_STATE, functioncode=3)
157        return self.SensorState(_sensor_state)

Get live sensor reading - Sensor State

Returns

Current state of the sensor

heater_voltage: float
169    @property
170    def heater_voltage(self) -> float:
171        """
172        Get sensor setting - heater voltage
173
174        :return: Current set heater voltage of the sensor (Volts)
175        :rtype: float
176        """
177        _heater_Voltage = self.read_register(
178            InputRegister.HEATER_VOLTAGE, functioncode=4
179        )
180        return _heater_Voltage / 100

Get sensor setting - heater voltage

Returns

Current set heater voltage of the sensor (Volts)

warnings: str
192    @property
193    def warnings(self) -> str:
194        """
195        Getting current warnings as a string of bits as received by the sensor
196
197        :return: Warning states as bit string
198        :rtype: str
199        """
200
201        warnings_hex = self.read_register(InputRegister.WARNINGS, functioncode=4)
202        
203        decode_to_bits = f"{warnings_hex:08b}"
204
205        return decode_to_bits

Getting current warnings as a string of bits as received by the sensor

Returns

Warning states as bit string

td_average: float
207    @property
208    def td_average(self) -> float:
209        """
210        Get live sensor TD average
211
212        :return: TD Average (ms)
213        :rtype: float
214        """
215        td_average = self.read_register(InputRegister.TD_AVERAGE, functioncode=4)
216        return td_average / 10

Get live sensor TD average

Returns

TD Average (ms)

td_raw: float
218    @property
219    def td_raw(self) -> float:
220        """
221        Get live sensor TD raw
222
223        :return: TD raw (ms)
224        :rtype: float
225        """
226        td_raw = self.read_register(InputRegister.TD_RAW, functioncode=4)
227        return td_raw / 10

Get live sensor TD raw

Returns

TD raw (ms)

tp: float
229    @property
230    def tp(self) -> float:
231        """
232        Get live sensor TP
233
234        :return: TP (ms)
235        :rtype: float
236        """
237        tp = self.read_register(InputRegister.TP, functioncode=4)
238        return tp / 10

Get live sensor TP

Returns

TP (ms)

t1: float
240    @property
241    def t1(self) -> float:
242        """
243        Get live sensor T1
244
245        :return: T1 (ms)
246        :rtype: float
247        """
248        t1 = self.read_register(InputRegister.T1, functioncode=4)
249        return t1 / 10

Get live sensor T1

Returns

T1 (ms)

t2: float
251    @property
252    def t2(self) -> float:
253        """
254        Get live sensor T2
255
256        :return: T2 (ms)
257        :rtype: float
258        """
259        t2 = self.read_register(InputRegister.T2, functioncode=4)
260        return t2 / 10

Get live sensor T2

Returns

T2 (ms)

t4: float
262    @property
263    def t4(self) -> float:
264        """
265        Get live sensor T4
266
267        :return: T4 (ms)
268        :rtype: float
269        """
270        t4 = self.read_register(InputRegister.T4, functioncode=4)
271        return t4 / 10

Get live sensor T4

Returns

T4 (ms)

t5: float
273    @property
274    def t5(self) -> float:
275        """
276        Get live sensor T5
277
278        :return: T5 (ms)
279        :rtype: float
280        """
281        t5 = self.read_register(InputRegister.T5, functioncode=4)
282        return t5 / 10

Get live sensor T5

Returns

T5 (ms)

pp_o2_real: float
284    @property
285    def pp_o2_real(self) -> float:
286        """
287        Get live ppO2 Real
288
289        :return: ppO2 Real
290        :rtype: float
291        """
292        pp_o2 = self.read_register(InputRegister.PPO2_REAL, functioncode=4)
293        return pp_o2 / 10

Get live ppO2 Real

Returns

ppO2 Real

pp_o2_raw: float
295    @property
296    def pp_o2_raw(self) -> float:
297        """
298        Get live ppO2 raw
299
300        :return: ppO2 raw
301        :rtype: float
302        """
303        pp_o2 = self.read_register(InputRegister.PPO2_RAW, functioncode=4)
304        return pp_o2 / 10

Get live ppO2 raw

Returns

ppO2 raw

pressure: float
306    @property
307    def pressure(self) -> float:
308        """
309        Get live pressure
310
311        :return: pressure (mbar)
312        :rtype: float
313        """
314        _pressure = self.read_register(InputRegister.PRESSURE, functioncode=4)
315        return _pressure

Get live pressure

Returns

pressure (mbar)

pressure_sens_temperature: float
317    @property
318    def pressure_sens_temperature(self) -> float:
319        """
320        Get live temperature of the pressure sensor
321
322        :return: temperature (°C)
323        :rtype: float
324        """
325        temp_decimal = self.read_register(
326            InputRegister.PRESSURE_SENS_TEMP, functioncode=4
327        )
328
329        converted_temperature = twos_compliment(temp_decimal, 16)
330
331        return converted_temperature

Get live temperature of the pressure sensor

Returns

temperature (°C)

calibration_status: OxyLc.CalibrationStatus
333    @property
334    def calibration_status(self) -> CalibrationStatus:
335        """
336        Get current calibration status of the sensor
337
338        :return: calibration status
339        :rtype: CalibrationStatus
340        """
341        _cal_status = self.read_register(
342            InputRegister.CALIBRATION_STATUS, functioncode=4
343        )
344        return self.CalibrationStatus(_cal_status)

Get current calibration status of the sensor

Returns

calibration status

year_of_manufacture: int
346    @property
347    def year_of_manufacture(self) -> int:
348        """
349        Get Year of Manufacture
350
351        :return: Year of Manufacture (YYYY)
352        :rtype: int
353        """
354        _year_of_manufacture = self.read_register(InputRegister.YOM, functioncode=4)
355        return _year_of_manufacture

Get Year of Manufacture

Returns

Year of Manufacture (YYYY)

day_of_manufacture: int
357    @property
358    def day_of_manufacture(self) -> int:
359        """
360        Get Day of Manufacture
361
362        :return: Day of Manufacture (DDD)
363        :rtype: int
364        """
365        _day_of_manufacture = self.read_register(InputRegister.DOM, functioncode=4)
366        return _day_of_manufacture

Get Day of Manufacture

Returns

Day of Manufacture (DDD)

serial_number: int
368    @property
369    def serial_number(self) -> int:
370        """
371        Get Serial Number
372
373        :return: Serial Number (XXXXX)
374        :rtype: int
375        """
376        _serial_number = self.read_register(InputRegister.SERIAL_NO, functioncode=4)
377        return _serial_number

Get Serial Number

Returns

Serial Number (XXXXX)

software_revision: int
379    @property
380    def software_revision(self) -> int:
381        """
382        Get Software Revision
383
384        :return: Software Revision (RRR)
385        :rtype: int
386        """
387        _software_revision = self.read_register(
388            InputRegister.SOFTWARE_REV, functioncode=4
389        )
390        return _software_revision

Get Software Revision

Returns

Software Revision (RRR)

calibration_value: float
392    @property
393    def calibration_value(self) -> float:
394        """
395        Return current calibration value
396        calibration_value defaults to 20.7 on new interface boards.
397
398        :return: calibration (%)
399        :rtype: float
400        """
401        _calibration_value = self.read_register(HoldingRegister.CALIBRATION_PERCENT)
402        return _calibration_value / 100

Return current calibration value calibration_value defaults to 20.7 on new interface boards.

Returns

calibration (%)

calibration_control: OxyLc.CalibrationControl
421    @property
422    def calibration_control(self) -> CalibrationControl:
423        """
424        Current Calibration control setting
425
426        :return: calibration control
427        :rtype: CalibrationControl
428        """
429        _calibration_control = self.read_register(HoldingRegister.CALIBRATION_CONTROL)
430        return self.CalibrationControl(_calibration_control)

Current Calibration control setting

Returns

calibration control

device_address: int
442    @property
443    def device_address(self) -> int:
444        """
445        Get device address
446
447        :return: Address (1-247)
448        :rtype: int
449        """
450        _address = self.read_register(HoldingRegister.ADDRESS)
451        return _address

Get device address

Returns

Address (1-247)

parity: OxyLc.Parity
467    @property
468    def parity(self) -> Parity:
469        """
470        Get device parity
471
472        :return: parity
473        :rtype: Parity
474        """
475        _parity = self.read_register(HoldingRegister.PARITY)
476        return self.Parity(_parity)

Get device parity

Returns

parity

def turn_on(self):
492    def turn_on(self):
493        """
494        Turn the sensor On
495        """
496        self.sensor_state = self.SensorState.ON

Turn the sensor On

def turn_off(self):
498    def turn_off(self):
499        """
500        Turn the sensor Off
501        """
502        self.sensor_state = self.SensorState.OFF

Turn the sensor Off

def calibrate(self, calibration_value: float | None = None) -> bool:
504    def calibrate(self, calibration_value: float | None = None) -> bool:
505        """
506        Calibrate the sensor to the given percent value
507
508        :param calibration_precent: O2 percent for calibration, If None is passed it will use the stored calibration_value.
509                                    calibration_value defaults to 20.7 on new interface boards.
510        :type calibration_precent: float, optional
511        :return: boolean indication of success
512        :rtype: bool
513        """
514        if self.status != self.StatusValues.OPERATING:
515            print("Sensor must be in operation to calibrate")
516            return False
517
518        if calibration_value:
519            try:
520                self.calibration_value = calibration_value
521            except ValueRangeError as e:
522                print(e)
523                return False
524
525        self.calibration_control = self.CalibrationControl.ACTIVATE
526
527        count = 0
528        timeout = 5
529        while (self.calibration_status != self.CalibrationStatus.COMPLETED) and (
530            count != timeout
531        ):
532            sleep(1)
533            count += 1
534
535        self.write_register(
536            HoldingRegister.CALIBRATION_CONTROL, self.CalibrationControl.RESET
537        )
538
539        if count == timeout:
540            print("calibration timeout")
541            return False
542        else:
543            return True

Calibrate the sensor to the given percent value

Parameters
  • calibration_precent: O2 percent for calibration, If None is passed it will use the stored calibration_value. calibration_value defaults to 20.7 on new interface boards.
Returns

boolean indication of success

def clear_error_flags(self) -> None:
545    def clear_error_flags(self) -> None:
546        """
547        Clears error flags on the device
548        """
549        self.write_register(HoldingRegister.CLEAR_FLAGS, 1)

Clears error flags on the device

def set_and_apply_heater_voltage(self, set_voltage: OxyLc.HeaterOptions | None) -> None:
551    def set_and_apply_heater_voltage(self, set_voltage: HeaterOptions | None) -> None:
552        """
553        Set the current heater voltage for the sensor
554
555        :param state: State option from Enum HeaterOptions
556        :type state: HeaterOptions
557        """
558        if set_voltage:
559            self.heater_voltage = set_voltage
560
561        # MinimalModbus will throw a NoResonseError when this register is written.
562        # It must be passed or it will crash the program
563        try:
564            self.write_register(
565                HoldingRegister.HEATER_VOLTAGE_SAVE, self.SaveAndApply.APPLY
566            )
567        except minimalmodbus.NoResponseError:
568            sleep(2)
569
570        self.sensor_state = self.SensorState.ON

Set the current heater voltage for the sensor

Parameters
  • state: State option from Enum HeaterOptions
def display_warnings(self) -> dict[slice(<class 'str'>, <class 'bool'>, None)]:
573    def display_warnings(self) -> dict[str:bool]:
574        """
575        Reads and decodes the warnings and returns a dictionary with each warning/error with a corresponding boolean showing error state
576
577        :return: Warning states for each of the representative bits (True is error)
578        :rtype: dict[str: bool]
579        """
580        warning_states = {
581            "Pump Error": False,
582            "Heater Voltage Error": False,
583            "Asymmetry Warning": False,
584            "O2 Low Warning": False,
585            "Pressure Sensor Warning": False,
586            "Pressure Sensor Error": False,
587        }
588        warnings_bits = self.warnings
589
590        for count, state in enumerate(warning_states):
591            if warnings_bits[::-1][count] == '1':
592                warning_states[state] = True
593
594        return warning_states

Reads and decodes the warnings and returns a dictionary with each warning/error with a corresponding boolean showing error state

Returns

Warning states for each of the representative bits (True is error)

class OxyLc.StatusValues(enum.IntEnum):
45    class StatusValues(IntEnum):
46        IDLE = 0
47        START_UP = 1
48        OPERATING = 2
49        SHUT_DOWN = 3
50        STANDBY = 4
IDLE = <StatusValues.IDLE: 0>
START_UP = <StatusValues.START_UP: 1>
OPERATING = <StatusValues.OPERATING: 2>
SHUT_DOWN = <StatusValues.SHUT_DOWN: 3>
STANDBY = <StatusValues.STANDBY: 4>
class OxyLc.SensorState(enum.IntEnum):
52    class SensorState(IntEnum):
53        OFF = 0
54        ON = 1
55        STANDBY = 2
OFF = <SensorState.OFF: 0>
ON = <SensorState.ON: 1>
STANDBY = <SensorState.STANDBY: 2>
class OxyLc.HeaterOptions(enum.IntEnum):
57    class HeaterOptions(IntEnum):
58        HEATER_4V0 = 0
59        HEATER_4V2 = 1
60        HEATER_4V35 = 2
61        HEATER_4V55 = 3
HEATER_4V0 = <HeaterOptions.HEATER_4V0: 0>
HEATER_4V2 = <HeaterOptions.HEATER_4V2: 1>
HEATER_4V35 = <HeaterOptions.HEATER_4V35: 2>
HEATER_4V55 = <HeaterOptions.HEATER_4V55: 3>
class OxyLc.SaveAndApply(enum.IntEnum):
63    class SaveAndApply(IntEnum):
64        IDLE = 0
65        APPLY = 1
IDLE = <SaveAndApply.IDLE: 0>
APPLY = <SaveAndApply.APPLY: 1>
class OxyLc.CalibrationStatus(enum.IntEnum):
67    class CalibrationStatus(IntEnum):
68        IDLE = 0
69        IN_PROGRESS = 1
70        COMPLETED = 2
IDLE = <CalibrationStatus.IDLE: 0>
IN_PROGRESS = <CalibrationStatus.IN_PROGRESS: 1>
COMPLETED = <CalibrationStatus.COMPLETED: 2>
class OxyLc.CalibrationControl(enum.IntEnum):
72    class CalibrationControl(IntEnum):
73        DEFAULT = 0
74        ACTIVATE = 1
75        RESET = 2
DEFAULT = <CalibrationControl.DEFAULT: 0>
ACTIVATE = <CalibrationControl.ACTIVATE: 1>
RESET = <CalibrationControl.RESET: 2>
class OxyLc.BaudRates(enum.IntEnum):
77    class BaudRates(IntEnum):
78        _2400 = 0
79        _4800 = 1
80        _9600 = 2
81        _19200 = 3
82        _38400 = 4
83        _57600 = 5
84        _115200 = 6
class OxyLc.Parity(enum.IntEnum):
86    class Parity(IntEnum):
87        NONE = 0
88        ODD = 1
89        EVEN = 2
NONE = <Parity.NONE: 0>
ODD = <Parity.ODD: 1>
EVEN = <Parity.EVEN: 2>
class OxyLc.StopBits(enum.IntEnum):
91    class StopBits(IntEnum):
92        _1 = 0
93        _2 = 1
class OxyLc.RS485ApplyChanges(enum.IntEnum):
95    class RS485ApplyChanges(IntEnum):
96        IDLE = 0
97        APPLY = 1
IDLE = <RS485ApplyChanges.IDLE: 0>
APPLY = <RS485ApplyChanges.APPLY: 1>