From 2fda3c88f99376e80255fcfb0793d75a31bda8dc Mon Sep 17 00:00:00 2001 From: "C.A.M. Gerlach" Date: Sun, 29 Mar 2020 22:56:01 -0500 Subject: [PATCH 01/13] Add AdafruitI2C class to read arbitrary Adafruit I2C devices --- setup.py | 6 ++++ src/brokkr/inputs/adafruiti2c.py | 53 ++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 src/brokkr/inputs/adafruiti2c.py diff --git a/setup.py b/setup.py index a3ac5d7..de10fc1 100644 --- a/setup.py +++ b/setup.py @@ -47,9 +47,15 @@ ], extras_require={ "all": [ + "Adafruit-Blinka", + "adafruit-circuitpython-busdevice", "pymodbus", "pyserial", ], + "adafruit": [ + "Adafruit-Blinka", + "adafruit-circuitpython-busdevice", + ], "modbus": [ "pymodbus", "pyserial", diff --git a/src/brokkr/inputs/adafruiti2c.py b/src/brokkr/inputs/adafruiti2c.py new file mode 100644 index 0000000..0e0b18f --- /dev/null +++ b/src/brokkr/inputs/adafruiti2c.py @@ -0,0 +1,53 @@ +""" +Input steps for arbitrary Adafruit I2C devices. +""" + +# Standard library imports +import importlib + +# Third party imports +import board +import busio + +# Local imports +import brokkr.pipeline.baseinput + + +class AdafruitI2CInput(brokkr.pipeline.baseinput.ValueInputStep): + def __init__( + self, + adafruit_module, + adafruit_class, + **value_input_kwargs): + super().__init__(binary_decoder=False, **value_input_kwargs) + + module_object = importlib.import_module(adafruit_module) + self.obj_class = getattr(module_object, adafruit_class) + + def read_raw_data(self, input_data=None): + try: + i2c = busio.I2C(board.SCL, board.SDA) + adafruit_obj = self.obj_class(i2c) + except Exception as e: + self.logger.error( + "%s initializing Adafruit I2C device %s on step %s: %s", + type(e).__name__, type(self.obj_class), self.name, e) + self.logger.info("Error details:", exc_info=True) + return None + + raw_data = [] + for data_type in self.data_types: + try: + data_value = getattr( + adafruit_obj, data_type.adafruit_property) + except Exception as e: + self.logger.error( + "%s getting attirbute %s from Adafruit I2C device %s " + "on step %s: %s", + type(e).__name__, data_type.adafruit_property, + type(self.obj_class), self.name, e) + self.logger.info("Error details:", exc_info=True) + data_value = None + raw_data.append(data_value) + + return raw_data From b413b01975dc09016d21450799a27c4d2109fdce Mon Sep 17 00:00:00 2001 From: "C.A.M. Gerlach" Date: Sun, 19 Apr 2020 22:15:30 -0500 Subject: [PATCH 02/13] Refactor property lookup input to be a base class & add I2C kwargs --- src/brokkr/inputs/adafruiti2c.py | 51 +++++++-------------------- src/brokkr/pipeline/baseinput.py | 59 ++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 39 deletions(-) diff --git a/src/brokkr/inputs/adafruiti2c.py b/src/brokkr/inputs/adafruiti2c.py index 0e0b18f..091d302 100644 --- a/src/brokkr/inputs/adafruiti2c.py +++ b/src/brokkr/inputs/adafruiti2c.py @@ -1,10 +1,7 @@ """ -Input steps for arbitrary Adafruit I2C devices. +Input steps for Adafruit digital I2C devices (e.g. SHT31, HTU21, BMP280, etc). """ -# Standard library imports -import importlib - # Third party imports import board import busio @@ -13,41 +10,17 @@ import brokkr.pipeline.baseinput -class AdafruitI2CInput(brokkr.pipeline.baseinput.ValueInputStep): +class AdafruitI2CInput(brokkr.pipeline.baseinput.PropertyInputStep): def __init__( self, - adafruit_module, - adafruit_class, - **value_input_kwargs): - super().__init__(binary_decoder=False, **value_input_kwargs) - - module_object = importlib.import_module(adafruit_module) - self.obj_class = getattr(module_object, adafruit_class) - - def read_raw_data(self, input_data=None): - try: - i2c = busio.I2C(board.SCL, board.SDA) - adafruit_obj = self.obj_class(i2c) - except Exception as e: - self.logger.error( - "%s initializing Adafruit I2C device %s on step %s: %s", - type(e).__name__, type(self.obj_class), self.name, e) - self.logger.info("Error details:", exc_info=True) - return None - - raw_data = [] - for data_type in self.data_types: - try: - data_value = getattr( - adafruit_obj, data_type.adafruit_property) - except Exception as e: - self.logger.error( - "%s getting attirbute %s from Adafruit I2C device %s " - "on step %s: %s", - type(e).__name__, data_type.adafruit_property, - type(self.obj_class), self.name, e) - self.logger.info("Error details:", exc_info=True) - data_value = None - raw_data.append(data_value) - + i2c_kwargs=None, + **property_input_kwargs): + super().__init__(**property_input_kwargs) + self._i2c_kwargs = {} if i2c_kwargs is None else i2c_kwargs + + def read_properties(self, sensor_object=None): + with busio.I2C(board.SCL, board.SDA, **self._i2c_kwargs) as i2c: + sensor_object = self.object_class( + i2c_bus=i2c, **self.sensor_kwargs) + raw_data = super().read_properties(sensor_object=sensor_object) return raw_data diff --git a/src/brokkr/pipeline/baseinput.py b/src/brokkr/pipeline/baseinput.py index 0b4365a..9cdf65a 100644 --- a/src/brokkr/pipeline/baseinput.py +++ b/src/brokkr/pipeline/baseinput.py @@ -4,6 +4,7 @@ # Standard library imports import abc +import importlib # Local imports import brokkr.pipeline.base @@ -11,6 +12,8 @@ import brokkr.pipeline.datavalue +# --- Core base classes --- # + class ValueInputStep(brokkr.pipeline.base.InputStep, metaclass=abc.ABCMeta): def __init__( self, @@ -65,3 +68,59 @@ def execute(self, input_data=None): raw_data = self.read_raw_data(input_data=input_data) output_data = self.decode_data(raw_data) return output_data + + +class PropertyInputStep(ValueInputStep): + def __init__( + self, + sensor_module, + sensor_class, + sensor_kwargs=None, + **value_input_kwargs): + super().__init__(binary_decoder=False, **value_input_kwargs) + self.sensor_kwargs = {} if sensor_kwargs is None else sensor_kwargs + self.sensor_object = None + + module_object = importlib.import_module(sensor_module) + self.object_class = getattr(module_object, sensor_class) + + def init_sensor_object(self): + try: + sensor_object = self.object_class(**self.sensor_kwargs) + except Exception as e: + self.logger.error( + "%s initializing %s sensor object %s on step %s: %s", + type(e).__name__, type(self), type(self.object_class), + self.name, e) + self.logger.info("Error details:", exc_info=True) + return None + else: + return sensor_object + + def read_properties(self, sensor_object=None): + if sensor_object is None: + sensor_object = self.sensor_object + + if sensor_object is None: + sensor_object = self.init_sensor_object() + if sensor_object is None: + return None + + raw_data = [] + for data_type in self.data_types: + try: + data_value = getattr(sensor_object, data_type.property_name) + except Exception as e: + self.logger.error( + "%s getting attirbute %s from %s sensor object %s " + "on step %s: %s", + type(e).__name__, data_type.property_name, type(self), + type(self.object_class), self.name, e) + self.logger.info("Error details:", exc_info=True) + data_value = None + raw_data.append(data_value) + return raw_data + + def read_raw_data(self, input_data=None): + raw_data = self.read_properties() + return raw_data From 0855b42ed73dd2c0dc6e0875d3ce04b7bf4b90e8 Mon Sep 17 00:00:00 2001 From: "C.A.M. Gerlach" Date: Mon, 20 Apr 2020 01:40:49 -0500 Subject: [PATCH 03/13] Add class for Adafruit Onewire sensors (e.g. DHT11/22) --- src/brokkr/inputs/adafruitonewire.py | 25 +++++++++++++++++++++++++ src/brokkr/pipeline/baseinput.py | 2 +- 2 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 src/brokkr/inputs/adafruitonewire.py diff --git a/src/brokkr/inputs/adafruitonewire.py b/src/brokkr/inputs/adafruitonewire.py new file mode 100644 index 0000000..70c7e48 --- /dev/null +++ b/src/brokkr/inputs/adafruitonewire.py @@ -0,0 +1,25 @@ +""" +Input steps for Adafruit Onewire devices (e.g. DHT11, DHT22). +""" + +# Local imports +import brokkr.pipeline.baseinput + + +class AdafruitOnewireInput(brokkr.pipeline.baseinput.PropertyInputStep): + def __init__(self, pin=None, sensor_kwargs=None, **property_input_kwargs): + if sensor_kwargs is None: + sensor_kwargs = {} + if pin is not None: + sensor_kwargs["pin"] = pin + + super().__init__(sensor_kwargs=sensor_kwargs, **property_input_kwargs) + self.sensor_object = self.init_sensor_object() + + def read_proprerties(self, sensor_object=None): + if sensor_object is None: + sensor_object = self.sensor_object + if sensor_object is None: + self.sensor_object = self.init_sensor_object() + + super().read_properties() diff --git a/src/brokkr/pipeline/baseinput.py b/src/brokkr/pipeline/baseinput.py index 9cdf65a..70f270b 100644 --- a/src/brokkr/pipeline/baseinput.py +++ b/src/brokkr/pipeline/baseinput.py @@ -112,7 +112,7 @@ def read_properties(self, sensor_object=None): data_value = getattr(sensor_object, data_type.property_name) except Exception as e: self.logger.error( - "%s getting attirbute %s from %s sensor object %s " + "%s getting attribute %s from %s sensor object %s " "on step %s: %s", type(e).__name__, data_type.property_name, type(self), type(self.object_class), self.name, e) From 36f33e1b0627a2584cfe7f4703ee2685a2f6cfd3 Mon Sep 17 00:00:00 2001 From: "C.A.M. Gerlach" Date: Tue, 21 Apr 2020 23:22:46 -0500 Subject: [PATCH 04/13] Add support for Adafruit ADS1x15 ADC --- src/brokkr/inputs/adafruitadc.py | 57 ++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 src/brokkr/inputs/adafruitadc.py diff --git a/src/brokkr/inputs/adafruitadc.py b/src/brokkr/inputs/adafruitadc.py new file mode 100644 index 0000000..e088e68 --- /dev/null +++ b/src/brokkr/inputs/adafruitadc.py @@ -0,0 +1,57 @@ +""" +Input steps for Adafruit digital I2C devices (e.g. SHT31, HTU21, BMP280, etc). +""" + +# Standard library imports +import importlib + +# Third party imports +import board +import busio + +# Local imports +import brokkr.pipeline.baseinput + + +DEFAULT_ADC_CHANNEL = 0 +DEFAULT_ADC_MODULE = "adafruit_ads1x15.ads1115" +DEFAULT_ADC_CLASS = "ADS1115" +DEFAULT_ANALOG_MODULE = "adafruit_ads1x15.analog_in" +DEFAULT_ANALOG_CLASS = "AnalogIn" + + +class AdafruitADCInput(brokkr.pipeline.baseinput.PropertyInputStep): + def __init__( + self, + sensor_module, + sensor_class, + adc_channel=None, + adc_kwargs=None, + analog_module=DEFAULT_ANALOG_MODULE, + analog_class=DEFAULT_ANALOG_CLASS, + i2c_kwargs=None, + **property_input_kwargs): + super().__init__( + sensor_module=sensor_module, + sensor_class=sensor_class, + **property_input_kwargs) + self._i2c_kwargs = {} if i2c_kwargs is None else i2c_kwargs + self._adc_kwargs = {} if adc_kwargs is None else adc_kwargs + + analog_object = importlib.import_module(analog_module) + self._analog_class = getattr(analog_object, analog_class) + + if (adc_channel is None + and self._adc_kwargs.get("channel", None) is None): + adc_channel = DEFAULT_ADC_CHANNEL + if adc_channel is not None: + self._adc_kwargs["positive_pin"] = adc_channel + + def read_properties(self, sensor_object=None): + with busio.I2C(board.SCL, board.SDA, **self._i2c_kwargs) as i2c: + sensor_object = self.object_class( + i2c=i2c, **self.sensor_kwargs) + channel_object = self._analog_class( + sensor_object, **self._adc_kwargs) + raw_data = super().read_properties(sensor_object=channel_object) + return raw_data From 641c07f8d45a99cde4ce6bf3de5794501892fd49 Mon Sep 17 00:00:00 2001 From: "C.A.M. Gerlach" Date: Thu, 23 Apr 2020 04:22:36 -0500 Subject: [PATCH 05/13] Use positional argument for Adafruit I2C parameter --- src/brokkr/inputs/adafruitadc.py | 2 +- src/brokkr/inputs/adafruiti2c.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/brokkr/inputs/adafruitadc.py b/src/brokkr/inputs/adafruitadc.py index e088e68..add217b 100644 --- a/src/brokkr/inputs/adafruitadc.py +++ b/src/brokkr/inputs/adafruitadc.py @@ -50,7 +50,7 @@ def __init__( def read_properties(self, sensor_object=None): with busio.I2C(board.SCL, board.SDA, **self._i2c_kwargs) as i2c: sensor_object = self.object_class( - i2c=i2c, **self.sensor_kwargs) + i2c, **self.sensor_kwargs) channel_object = self._analog_class( sensor_object, **self._adc_kwargs) raw_data = super().read_properties(sensor_object=channel_object) diff --git a/src/brokkr/inputs/adafruiti2c.py b/src/brokkr/inputs/adafruiti2c.py index 091d302..5fcecc7 100644 --- a/src/brokkr/inputs/adafruiti2c.py +++ b/src/brokkr/inputs/adafruiti2c.py @@ -21,6 +21,6 @@ def __init__( def read_properties(self, sensor_object=None): with busio.I2C(board.SCL, board.SDA, **self._i2c_kwargs) as i2c: sensor_object = self.object_class( - i2c_bus=i2c, **self.sensor_kwargs) + i2c, **self.sensor_kwargs) raw_data = super().read_properties(sensor_object=sensor_object) return raw_data From 6cdc80eaf4511ac2c2669c0f6b35857d0ebce52c Mon Sep 17 00:00:00 2001 From: "C.A.M. Gerlach" Date: Mon, 27 Apr 2020 20:27:54 -0500 Subject: [PATCH 06/13] Add GPIO counter device and input class --- setup.py | 6 ++ src/brokkr/inputs/adafruitonewire.py | 5 +- src/brokkr/inputs/gpiocounter.py | 89 ++++++++++++++++++++++++++++ 3 files changed, 97 insertions(+), 3 deletions(-) create mode 100644 src/brokkr/inputs/gpiocounter.py diff --git a/setup.py b/setup.py index de10fc1..f1561c5 100644 --- a/setup.py +++ b/setup.py @@ -49,13 +49,19 @@ "all": [ "Adafruit-Blinka", "adafruit-circuitpython-busdevice", + "gpiozero", "pymodbus", "pyserial", + "RPi.GPIO", ], "adafruit": [ "Adafruit-Blinka", "adafruit-circuitpython-busdevice", ], + "gpio": [ + "gpiozero", + "RPi.GPIO", + ], "modbus": [ "pymodbus", "pyserial", diff --git a/src/brokkr/inputs/adafruitonewire.py b/src/brokkr/inputs/adafruitonewire.py index 70c7e48..4cb6e83 100644 --- a/src/brokkr/inputs/adafruitonewire.py +++ b/src/brokkr/inputs/adafruitonewire.py @@ -7,11 +7,10 @@ class AdafruitOnewireInput(brokkr.pipeline.baseinput.PropertyInputStep): - def __init__(self, pin=None, sensor_kwargs=None, **property_input_kwargs): + def __init__(self, pin, sensor_kwargs=None, **property_input_kwargs): if sensor_kwargs is None: sensor_kwargs = {} - if pin is not None: - sensor_kwargs["pin"] = pin + sensor_kwargs["pin"] = pin super().__init__(sensor_kwargs=sensor_kwargs, **property_input_kwargs) self.sensor_object = self.init_sensor_object() diff --git a/src/brokkr/inputs/gpiocounter.py b/src/brokkr/inputs/gpiocounter.py new file mode 100644 index 0000000..b806f5f --- /dev/null +++ b/src/brokkr/inputs/gpiocounter.py @@ -0,0 +1,89 @@ +""" +Input steps for digital GPIO devices. +""" + +# Standard library imports +import collections +import time + +# Third party imports +import gpiozero + +# Local imports +import brokkr.pipeline.baseinput +import brokkr.utils.misc + + +class GPIOCounterDevice(brokkr.utils.misc.AutoReprMixin): + def __init__(self, pin, max_counts=None, gpio_kwargs=None): + self._gpio_kwargs = {} if gpio_kwargs is None else gpio_kwargs + self._gpio_kwargs["pin"] = pin + + self._count_times = collections.deque(maxlen=max_counts) + self._gpio_device = gpiozero.DigitalInputDevice(**gpio_kwargs) + self._gpio_device.when_activated = self._count + + self.start_time = time.monotonic() + + def _count(self): + """Count one transition. Used as a callback.""" + self._count_times.append(time.monotonic()) + + @property + def time_elapsed_s(self): + """The time elapsed, in s, since the start time was last reset.""" + return time.monotonic() - self.start_time + + def get_count(self, period_s=None, mean=False): + if not period_s: + count = len(self._count_times) + else: + # Tabulate the number of counts over a given period + count = -1 + for count, count_time in enumerate(reversed(self._count_times)): + if count_time < (time.monotonic() - period_s): + break + else: + count += 1 + + if mean: + count = count / max([min([self.time_elapsed_s, period_s]), + time.get_clock_info("time").resolution]) + + return count + + def reset(self): + """ + Reset the count to zero and start time to the current time. + + Returns + ------- + None. + + """ + self._count_times.clear() + self.start_time = time.monotonic() + + +class GPIOCounterInput(brokkr.pipeline.baseinput.ValueInputStep): + def __init__( + self, + pin, + max_counts=None, + gpio_kwargs=None, + reset_after_read=False, + **value_input_kwargs): + super().__init__(**value_input_kwargs) + self._counter_device = GPIOCounterDevice( + pin=pin, max_counts=max_counts, gpio_kwargs=gpio_kwargs) + self._reset_after_read = reset_after_read + + def read_raw_data(self, input_data=None): + raw_data = [ + self._counter_device.get_count( + period_s=getattr(data_type, "period_s", None), + mean=getattr(data_type, "mean", False), + ) for data_type in self.data_types] + if self._reset_after_read: + self._counter_device.reset() + return raw_data From 023f962995bf881fdf7ee2d2ce68493193021fc6 Mon Sep 17 00:00:00 2001 From: "C.A.M. Gerlach" Date: Tue, 28 Apr 2020 01:43:57 -0500 Subject: [PATCH 07/13] Fix error handling for Adafruit I2C and ADC classes --- src/brokkr/inputs/adafruitadc.py | 8 +++++--- src/brokkr/inputs/adafruiti2c.py | 5 +++-- src/brokkr/pipeline/baseinput.py | 12 ++++++++++-- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/brokkr/inputs/adafruitadc.py b/src/brokkr/inputs/adafruitadc.py index add217b..af53986 100644 --- a/src/brokkr/inputs/adafruitadc.py +++ b/src/brokkr/inputs/adafruitadc.py @@ -49,9 +49,11 @@ def __init__( def read_properties(self, sensor_object=None): with busio.I2C(board.SCL, board.SDA, **self._i2c_kwargs) as i2c: - sensor_object = self.object_class( - i2c, **self.sensor_kwargs) + sensor_object = self.init_sensor_object(i2c) + if not sensor_object: + return None channel_object = self._analog_class( sensor_object, **self._adc_kwargs) - raw_data = super().read_properties(sensor_object=channel_object) + raw_data = super().read_properties( + sensor_object=channel_object) return raw_data diff --git a/src/brokkr/inputs/adafruiti2c.py b/src/brokkr/inputs/adafruiti2c.py index 5fcecc7..4217d46 100644 --- a/src/brokkr/inputs/adafruiti2c.py +++ b/src/brokkr/inputs/adafruiti2c.py @@ -20,7 +20,8 @@ def __init__( def read_properties(self, sensor_object=None): with busio.I2C(board.SCL, board.SDA, **self._i2c_kwargs) as i2c: - sensor_object = self.object_class( - i2c, **self.sensor_kwargs) + sensor_object = self.init_sensor_object(i2c) + if not sensor_object: + return None raw_data = super().read_properties(sensor_object=sensor_object) return raw_data diff --git a/src/brokkr/pipeline/baseinput.py b/src/brokkr/pipeline/baseinput.py index 70f270b..f49a7a9 100644 --- a/src/brokkr/pipeline/baseinput.py +++ b/src/brokkr/pipeline/baseinput.py @@ -75,18 +75,26 @@ def __init__( self, sensor_module, sensor_class, + sensor_args=None, sensor_kwargs=None, **value_input_kwargs): super().__init__(binary_decoder=False, **value_input_kwargs) + self.sensor_args = () if sensor_args is None else sensor_args self.sensor_kwargs = {} if sensor_kwargs is None else sensor_kwargs self.sensor_object = None module_object = importlib.import_module(sensor_module) self.object_class = getattr(module_object, sensor_class) - def init_sensor_object(self): + def init_sensor_object(self, *sensor_args, **sensor_kwargs): + if not sensor_args: + sensor_args = self.sensor_args + if not sensor_kwargs: + sensor_kwargs = self.sensor_kwargs + try: - sensor_object = self.object_class(**self.sensor_kwargs) + sensor_object = self.object_class( + *sensor_args, **sensor_kwargs) except Exception as e: self.logger.error( "%s initializing %s sensor object %s on step %s: %s", From 41bc65450fc4525d21e4fc614908a55db2b1e30d Mon Sep 17 00:00:00 2001 From: "C.A.M. Gerlach" Date: Wed, 29 Apr 2020 14:08:19 -0500 Subject: [PATCH 08/13] Add basic support for sensor packages with read methods (e.g. DSxxxx) --- .pylintrc | 2 +- src/brokkr/.pylintrc | 2 +- src/brokkr/inputs/adafruitadc.py | 4 +-- src/brokkr/inputs/adafruiti2c.py | 4 +-- src/brokkr/inputs/adafruitonewire.py | 5 ++-- src/brokkr/pipeline/baseinput.py | 37 ++++++++++++++++++++++------ 6 files changed, 39 insertions(+), 15 deletions(-) diff --git a/.pylintrc b/.pylintrc index 1587017..9aaac64 100644 --- a/.pylintrc +++ b/.pylintrc @@ -452,7 +452,7 @@ max-branches=12 max-locals=20 # Maximum number of parents for a class (see R0901). -max-parents=7 +max-parents=10 # Maximum number of public methods for a class (see R0904). max-public-methods=20 diff --git a/src/brokkr/.pylintrc b/src/brokkr/.pylintrc index 1587017..9aaac64 100644 --- a/src/brokkr/.pylintrc +++ b/src/brokkr/.pylintrc @@ -452,7 +452,7 @@ max-branches=12 max-locals=20 # Maximum number of parents for a class (see R0901). -max-parents=7 +max-parents=10 # Maximum number of public methods for a class (see R0904). max-public-methods=20 diff --git a/src/brokkr/inputs/adafruitadc.py b/src/brokkr/inputs/adafruitadc.py index af53986..d8f97bf 100644 --- a/src/brokkr/inputs/adafruitadc.py +++ b/src/brokkr/inputs/adafruitadc.py @@ -47,13 +47,13 @@ def __init__( if adc_channel is not None: self._adc_kwargs["positive_pin"] = adc_channel - def read_properties(self, sensor_object=None): + def read_sensor_data(self, sensor_object=None): with busio.I2C(board.SCL, board.SDA, **self._i2c_kwargs) as i2c: sensor_object = self.init_sensor_object(i2c) if not sensor_object: return None channel_object = self._analog_class( sensor_object, **self._adc_kwargs) - raw_data = super().read_properties( + raw_data = super().read_sensor_data( sensor_object=channel_object) return raw_data diff --git a/src/brokkr/inputs/adafruiti2c.py b/src/brokkr/inputs/adafruiti2c.py index 4217d46..2655f57 100644 --- a/src/brokkr/inputs/adafruiti2c.py +++ b/src/brokkr/inputs/adafruiti2c.py @@ -18,10 +18,10 @@ def __init__( super().__init__(**property_input_kwargs) self._i2c_kwargs = {} if i2c_kwargs is None else i2c_kwargs - def read_properties(self, sensor_object=None): + def read_sensor_data(self, sensor_object=None): with busio.I2C(board.SCL, board.SDA, **self._i2c_kwargs) as i2c: sensor_object = self.init_sensor_object(i2c) if not sensor_object: return None - raw_data = super().read_properties(sensor_object=sensor_object) + raw_data = super().read_sensor_data(sensor_object=sensor_object) return raw_data diff --git a/src/brokkr/inputs/adafruitonewire.py b/src/brokkr/inputs/adafruitonewire.py index 4cb6e83..4ab15bc 100644 --- a/src/brokkr/inputs/adafruitonewire.py +++ b/src/brokkr/inputs/adafruitonewire.py @@ -15,10 +15,11 @@ def __init__(self, pin, sensor_kwargs=None, **property_input_kwargs): super().__init__(sensor_kwargs=sensor_kwargs, **property_input_kwargs) self.sensor_object = self.init_sensor_object() - def read_proprerties(self, sensor_object=None): + def read_sensor_data(self, sensor_object=None): if sensor_object is None: sensor_object = self.sensor_object if sensor_object is None: self.sensor_object = self.init_sensor_object() - super().read_properties() + sensor_data = super().read_sensor_data() + return sensor_data diff --git a/src/brokkr/pipeline/baseinput.py b/src/brokkr/pipeline/baseinput.py index f49a7a9..0c8353a 100644 --- a/src/brokkr/pipeline/baseinput.py +++ b/src/brokkr/pipeline/baseinput.py @@ -70,7 +70,7 @@ def execute(self, input_data=None): return output_data -class PropertyInputStep(ValueInputStep): +class SensorInputStep(ValueInputStep, metaclass=abc.ABCMeta): def __init__( self, sensor_module, @@ -105,7 +105,11 @@ def init_sensor_object(self, *sensor_args, **sensor_kwargs): else: return sensor_object - def read_properties(self, sensor_object=None): + @abc.abstractmethod + def read_sensor_value(self, sensor_object, data_type): + pass + + def read_sensor_data(self, sensor_object=None): if sensor_object is None: sensor_object = self.sensor_object @@ -114,10 +118,11 @@ def read_properties(self, sensor_object=None): if sensor_object is None: return None - raw_data = [] + sensor_data = [] for data_type in self.data_types: try: - data_value = getattr(sensor_object, data_type.property_name) + data_value = self.read_sensor_value( + sensor_object=sensor_object, data_type=data_type) except Exception as e: self.logger.error( "%s getting attribute %s from %s sensor object %s " @@ -126,9 +131,27 @@ def read_properties(self, sensor_object=None): type(self.object_class), self.name, e) self.logger.info("Error details:", exc_info=True) data_value = None - raw_data.append(data_value) - return raw_data + sensor_data.append(data_value) + return sensor_data def read_raw_data(self, input_data=None): - raw_data = self.read_properties() + raw_data = self.read_sensor_data() return raw_data + + +class PropertyInputStep(SensorInputStep): + def read_sensor_value(self, sensor_object, data_type): + if sensor_object is None: + sensor_object = self.sensor_object + data_value = getattr(sensor_object, data_type.property_name) + return data_value + + +class MethodInputStep(SensorInputStep): + def read_sensor_value(self, sensor_object, data_type): + if sensor_object is None: + sensor_object = self.sensor_object + function_kwargs = getattr(data_type, "function_kwargs", {}) + data_value = getattr(sensor_object, data_type.function_name)( + **function_kwargs) + return data_value From 802528d2890458d2a1f402cb6ce3c491c310774d Mon Sep 17 00:00:00 2001 From: "C.A.M. Gerlach" Date: Thu, 30 Apr 2020 20:47:45 -0500 Subject: [PATCH 09/13] Refactor and add persistant Adafruit I2C input for Si7021 --- src/brokkr/inputs/adafruiti2c.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/brokkr/inputs/adafruiti2c.py b/src/brokkr/inputs/adafruiti2c.py index 2655f57..388875b 100644 --- a/src/brokkr/inputs/adafruiti2c.py +++ b/src/brokkr/inputs/adafruiti2c.py @@ -10,7 +10,7 @@ import brokkr.pipeline.baseinput -class AdafruitI2CInput(brokkr.pipeline.baseinput.PropertyInputStep): +class BaseAdafruitI2CInput(brokkr.pipeline.baseinput.PropertyInputStep): def __init__( self, i2c_kwargs=None, @@ -18,6 +18,8 @@ def __init__( super().__init__(**property_input_kwargs) self._i2c_kwargs = {} if i2c_kwargs is None else i2c_kwargs + +class AdafruitI2CInput(BaseAdafruitI2CInput): def read_sensor_data(self, sensor_object=None): with busio.I2C(board.SCL, board.SDA, **self._i2c_kwargs) as i2c: sensor_object = self.init_sensor_object(i2c) @@ -25,3 +27,16 @@ def read_sensor_data(self, sensor_object=None): return None raw_data = super().read_sensor_data(sensor_object=sensor_object) return raw_data + + +class AdafruitPersistantI2CInput(BaseAdafruitI2CInput): + def __init__(self, **adafruit_i2c_kwargs): + super().__init__(**adafruit_i2c_kwargs) + self.sensor_object = self.init_sensor_object() + + def init_sensor_object(self, *sensor_args, **sensor_kwargs): + i2c = busio.I2C(board.SCL, board.SDA, **self._i2c_kwargs) + sensor_object = super().init_sensor_object( + i2c, *sensor_args, **sensor_kwargs) + self.sensor_object = sensor_object + return sensor_object From eedde5877fbcc31200aee03b01a14becf54b06bf Mon Sep 17 00:00:00 2001 From: "C.A.M. Gerlach" Date: Thu, 7 May 2020 17:15:59 -0500 Subject: [PATCH 10/13] Simplify and improve optional caching of Adafruit sensor objects --- src/brokkr/inputs/adafruitadc.py | 3 ++- src/brokkr/inputs/adafruiti2c.py | 8 ++++---- src/brokkr/inputs/adafruitonewire.py | 15 ++++----------- src/brokkr/pipeline/baseinput.py | 12 +++++++++--- 4 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/brokkr/inputs/adafruitadc.py b/src/brokkr/inputs/adafruitadc.py index d8f97bf..7033340 100644 --- a/src/brokkr/inputs/adafruitadc.py +++ b/src/brokkr/inputs/adafruitadc.py @@ -34,6 +34,7 @@ def __init__( super().__init__( sensor_module=sensor_module, sensor_class=sensor_class, + cache_sensor_object=False, **property_input_kwargs) self._i2c_kwargs = {} if i2c_kwargs is None else i2c_kwargs self._adc_kwargs = {} if adc_kwargs is None else adc_kwargs @@ -42,7 +43,7 @@ def __init__( self._analog_class = getattr(analog_object, analog_class) if (adc_channel is None - and self._adc_kwargs.get("channel", None) is None): + and self._adc_kwargs.get("positive_pin", None) is None): adc_channel = DEFAULT_ADC_CHANNEL if adc_channel is not None: self._adc_kwargs["positive_pin"] = adc_channel diff --git a/src/brokkr/inputs/adafruiti2c.py b/src/brokkr/inputs/adafruiti2c.py index 388875b..c7d38d3 100644 --- a/src/brokkr/inputs/adafruiti2c.py +++ b/src/brokkr/inputs/adafruiti2c.py @@ -30,13 +30,13 @@ def read_sensor_data(self, sensor_object=None): class AdafruitPersistantI2CInput(BaseAdafruitI2CInput): - def __init__(self, **adafruit_i2c_kwargs): - super().__init__(**adafruit_i2c_kwargs) - self.sensor_object = self.init_sensor_object() + def __init__( + self, + **adafruit_i2c_input_kwargs): + super().__init__(cache_sensor_object=True, **adafruit_i2c_input_kwargs) def init_sensor_object(self, *sensor_args, **sensor_kwargs): i2c = busio.I2C(board.SCL, board.SDA, **self._i2c_kwargs) sensor_object = super().init_sensor_object( i2c, *sensor_args, **sensor_kwargs) - self.sensor_object = sensor_object return sensor_object diff --git a/src/brokkr/inputs/adafruitonewire.py b/src/brokkr/inputs/adafruitonewire.py index 4ab15bc..8a6a58a 100644 --- a/src/brokkr/inputs/adafruitonewire.py +++ b/src/brokkr/inputs/adafruitonewire.py @@ -12,14 +12,7 @@ def __init__(self, pin, sensor_kwargs=None, **property_input_kwargs): sensor_kwargs = {} sensor_kwargs["pin"] = pin - super().__init__(sensor_kwargs=sensor_kwargs, **property_input_kwargs) - self.sensor_object = self.init_sensor_object() - - def read_sensor_data(self, sensor_object=None): - if sensor_object is None: - sensor_object = self.sensor_object - if sensor_object is None: - self.sensor_object = self.init_sensor_object() - - sensor_data = super().read_sensor_data() - return sensor_data + super().__init__( + sensor_kwargs=sensor_kwargs, + cache_sensor_object=True, + **property_input_kwargs) diff --git a/src/brokkr/pipeline/baseinput.py b/src/brokkr/pipeline/baseinput.py index 0c8353a..fd92eb6 100644 --- a/src/brokkr/pipeline/baseinput.py +++ b/src/brokkr/pipeline/baseinput.py @@ -77,11 +77,13 @@ def __init__( sensor_class, sensor_args=None, sensor_kwargs=None, + cache_sensor_object=False, **value_input_kwargs): super().__init__(binary_decoder=False, **value_input_kwargs) self.sensor_args = () if sensor_args is None else sensor_args self.sensor_kwargs = {} if sensor_kwargs is None else sensor_kwargs self.sensor_object = None + self.cache_sensor_object = cache_sensor_object module_object = importlib.import_module(sensor_module) self.object_class = getattr(module_object, sensor_class) @@ -101,9 +103,13 @@ def init_sensor_object(self, *sensor_args, **sensor_kwargs): type(e).__name__, type(self), type(self.object_class), self.name, e) self.logger.info("Error details:", exc_info=True) - return None - else: - return sensor_object + self.logger.info("Sensor args: %r | Sensor kwargs: %r:", + sensor_args, sensor_kwargs) + sensor_object = None + + if self.cache_sensor_object: + self.sensor_object = sensor_object + return sensor_object @abc.abstractmethod def read_sensor_value(self, sensor_object, data_type): From 81c742c934ec4f2d0a2109d6d1c1db2c6acbb040 Mon Sep 17 00:00:00 2001 From: "C.A.M. Gerlach" Date: Fri, 8 May 2020 20:00:07 -0500 Subject: [PATCH 11/13] Add more powerful data conversion features to decode --- .pylintrc | 2 +- setup.py | 1 + src/brokkr/.pylintrc | 2 +- src/brokkr/pipeline/datavalue.py | 5 ++- src/brokkr/pipeline/decode.py | 70 ++++++++++++++++++++++++++++---- 5 files changed, 67 insertions(+), 13 deletions(-) diff --git a/.pylintrc b/.pylintrc index 9aaac64..60da777 100644 --- a/.pylintrc +++ b/.pylintrc @@ -437,7 +437,7 @@ valid-metaclass-classmethod-first-arg=cls [DESIGN] # Maximum number of arguments for function / method. -max-args=12 +max-args=15 # Maximum number of attributes for a class (see R0902). max-attributes=15 diff --git a/setup.py b/setup.py index f1561c5..b38284f 100644 --- a/setup.py +++ b/setup.py @@ -43,6 +43,7 @@ install_requires=[ "packaging", "serviceinstaller >= 0.1.3 ; sys_platform=='linux'", + "simpleeval", "toml", ], extras_require={ diff --git a/src/brokkr/.pylintrc b/src/brokkr/.pylintrc index 9aaac64..60da777 100644 --- a/src/brokkr/.pylintrc +++ b/src/brokkr/.pylintrc @@ -437,7 +437,7 @@ valid-metaclass-classmethod-first-arg=cls [DESIGN] # Maximum number of arguments for function / method. -max-args=12 +max-args=15 # Maximum number of attributes for a class (see R0902). max-attributes=15 diff --git a/src/brokkr/pipeline/datavalue.py b/src/brokkr/pipeline/datavalue.py index cb516f9..f7d8e69 100644 --- a/src/brokkr/pipeline/datavalue.py +++ b/src/brokkr/pipeline/datavalue.py @@ -13,6 +13,7 @@ def __init__( conversion=True, binary_type=None, input_type=None, + digits=None, na_marker=None, full_name=None, unit=None, @@ -20,12 +21,12 @@ def __init__( range_min=None, range_max=None, custom_attrs=None, - **conversion_kwargs, - ): + **conversion_kwargs): self.name = name self.conversion = conversion self.binary_type = binary_type self.input_type = binary_type if input_type is None else input_type + self.digits = digits self.na_marker = na_marker self.conversion_kwargs = conversion_kwargs diff --git a/src/brokkr/pipeline/decode.py b/src/brokkr/pipeline/decode.py index d0e5386..a34bd78 100644 --- a/src/brokkr/pipeline/decode.py +++ b/src/brokkr/pipeline/decode.py @@ -3,10 +3,16 @@ """ # Standard library imports +import ast import datetime import logging +import math +import operator import struct +# Third party imports +import simpleeval + # Local imports import brokkr.pipeline.datavalue import brokkr.utils.misc @@ -16,6 +22,15 @@ OUTPUT_CUSTOM = "custom" +EVAL_OPERATORS_EXTRA = { + ast.BitAnd: operator.and_, + ast.BitOr: operator.or_, + ast.BitXor: operator.xor, + ast.Invert: operator.invert, + ast.RShift: operator.rshift, + ast.RShift: operator.rshift, + } + def _convert_none(value): # pylint: disable=unused-argument @@ -30,6 +45,10 @@ def _convert_bitfield(value): return int(value) +def _convert_bool(value): + return bool(value) + + def _convert_byte(value): return int(value) @@ -46,8 +65,8 @@ def _convert_float(value): return float(value) -def _convert_int(value): - return int(value) +def _convert_int(value, **kwargs): + return int(value, **kwargs) def _convert_str(value): @@ -77,10 +96,23 @@ def _convert_timestamp(value, time_format="%Y-%m-%d %H:%M:%S"): return datetime.datetime.strptime(value, time_format) +def _convert_custom(value, base=2, power=0, scale=1, offset=0): + return value * (base ** power) * scale + offset + + +def _convert_eval(value, expression): + value_parser = simpleeval.SimpleEval(names={"value": value}) + value_parser.operators = { + **value_parser.operators, **EVAL_OPERATORS_EXTRA} + value = value_parser.eval(expression) + return value + + CONVERSION_FUNCTIONS = { False: _convert_none, True: _convert_pass, "bitfield": _convert_bitfield, + "bool": _convert_bool, "byte": _convert_byte, "bytestr": _convert_bytestr, "bytestr_strip": _convert_bytestr_strip, @@ -90,21 +122,36 @@ def _convert_timestamp(value, time_format="%Y-%m-%d %H:%M:%S"): "time_posix": _convert_time_posix, "time_posix_ms": _convert_time_posix_ms, "timestamp": _convert_timestamp, + "custom": _convert_custom, + "eval": _convert_eval, } -def convert_custom( - value, scale=1, offset=0, base=2, power=0, digits=None, after=None, - **after_kwargs): - value = value * (base ** power) * scale + offset - if digits is not None: - value = round(value, digits) +def convert_multistep( + value, + before=None, + before_kwargs=None, + main=None, + after=None, + after_kwargs=None, + **main_kwargs, + ): + if before_kwargs is None: + before_kwargs = {} + if after_kwargs is None: + after_kwargs = {} + + if before is not None: + value = CONVERSION_FUNCTIONS[before](value, **before_kwargs) + if main is not None: + value = CONVERSION_FUNCTIONS[main](value, **main_kwargs) if after is not None: value = CONVERSION_FUNCTIONS[after](value, **after_kwargs) + return value -CONVERSION_FUNCTIONS["custom"] = convert_custom +CONVERSION_FUNCTIONS["multistep"] = convert_multistep LOGGER = logging.getLogger(__name__) @@ -166,6 +213,8 @@ def convert_data(self, raw_data): output_value = ( self.conversion_functions[data_type.conversion]( value, **data_type.conversion_kwargs)) + if data_type.digits is not None: + output_value = round(output_value, data_type.digits) # Handle errors decoding specific values except Exception as e: if error_count < 1: @@ -190,6 +239,9 @@ def convert_data(self, raw_data): 1, **data_type.conversion_kwargs) - self.conversion_functions[data_type.conversion]( 0, **data_type.conversion_kwargs)) + uncertainty = round( + uncertainty, -int(math.floor(math.log10(uncertainty)))) + else: uncertainty = data_type.uncertainty data_value = brokkr.pipeline.datavalue.DataValue( From 29b4fc7f2a62364cad593f8b5afdc323886d671f Mon Sep 17 00:00:00 2001 From: "C.A.M. Gerlach" Date: Sat, 9 May 2020 22:01:46 -0500 Subject: [PATCH 12/13] Add support for arbitrary I2C and SMBus devices with smbus2 --- setup.py | 4 ++ src/brokkr/inputs/smbusi2c.py | 103 +++++++++++++++++++++++++++++++ src/brokkr/pipeline/baseinput.py | 58 ++++++++++------- src/brokkr/pipeline/decode.py | 16 +++-- 4 files changed, 156 insertions(+), 25 deletions(-) create mode 100644 src/brokkr/inputs/smbusi2c.py diff --git a/setup.py b/setup.py index b38284f..2da8249 100644 --- a/setup.py +++ b/setup.py @@ -54,6 +54,7 @@ "pymodbus", "pyserial", "RPi.GPIO", + "smbus2", ], "adafruit": [ "Adafruit-Blinka", @@ -67,6 +68,9 @@ "pymodbus", "pyserial", ], + "smbus": [ + "smbus2", + ], }, entry_points={ "console_scripts": [ diff --git a/src/brokkr/inputs/smbusi2c.py b/src/brokkr/inputs/smbusi2c.py new file mode 100644 index 0000000..f0db663 --- /dev/null +++ b/src/brokkr/inputs/smbusi2c.py @@ -0,0 +1,103 @@ +""" +Generalized input class for an I2C/SMBus device using the SMBus library. +""" + +# Standard library imports +from pathlib import Path + +# Third party imports +import smbus2 + +# Local imports +import brokkr.pipeline.baseinput +import brokkr.utils.misc + + +MAX_I2C_BUS_N = 6 + +I2C_BLOCK_READ_FUNCTION = "read_i2c_block_data" +DEFAULT_READ_FUNCTION = I2C_BLOCK_READ_FUNCTION + + +class SMBusI2CDevice(brokkr.utils.misc.AutoReprMixin): + def __init__(self, bus=None, force=None): + self.force = force + + # Automatically try to find first I2C bus and use that + if bus is None: + for n_bus in range(0, MAX_I2C_BUS_N + 1): + if Path(f"/dev/i2c-{n_bus}").exists(): + bus = n_bus + break + else: + raise RuntimeError("Could not find I2C any bus device") + + self.bus = bus + + def read(self, force=None, + read_function=DEFAULT_READ_FUNCTION, **read_kwargs): + if force is None: + force = self.force + with smbus2.SMBus(self.bus, force=self.force) as i2c_bus: + buffer = getattr(i2c_bus, read_function)( + force=force, **read_kwargs) + return buffer + + +class SMBusI2CInput(brokkr.pipeline.baseinput.SensorInputStep): + def __init__( + self, + bus=None, + read_function=DEFAULT_READ_FUNCTION, + init_kwargs=None, + read_kwargs=None, + include_all_data_each=True, + **sensor_input_kwargs): + init_kwargs = {} if init_kwargs is None else init_kwargs + self._read_kwargs = {} if read_kwargs is None else read_kwargs + self._read_function = read_function + sensor_kwargs = {"bus": bus, **init_kwargs} + + super().__init__( + sensor_class=SMBusI2CDevice, + sensor_kwargs=sensor_kwargs, + include_all_data_each=include_all_data_each, + **sensor_input_kwargs) + + def read_sensor_data(self, sensor_object=None): + sensor_object = self.get_sensor_object(sensor_object=sensor_object) + if sensor_object is None: + return None + + try: + sensor_data = sensor_object.read( + read_function=self._read_function, **self._read_kwargs) + except Exception as e: + self.logger.error( + "%s reading data from I2C SMBus device with function %s " + "of %s sensor object %s on step %s: %s", + type(e).__name__, self._read_function, + type(self), type(self.object_class), self.name, e) + self.logger.info("Error details:", exc_info=True) + sensor_data = None + + return sensor_data + + +class SMBusI2CBlockInput(SMBusI2CInput): + def __init__( + self, + i2c_addr, + register=0, + length=1, + force=None, + **smbus_input_kwargs): + read_kwargs = { + "i2c_addr": i2c_addr, + "register": register, + "length": length, + "force": force, + } + + super().__init__(read_function=I2C_BLOCK_READ_FUNCTION, + read_kwargs=read_kwargs, **smbus_input_kwargs) diff --git a/src/brokkr/pipeline/baseinput.py b/src/brokkr/pipeline/baseinput.py index fd92eb6..4ade76b 100644 --- a/src/brokkr/pipeline/baseinput.py +++ b/src/brokkr/pipeline/baseinput.py @@ -22,6 +22,7 @@ def __init__( datatype_default_kwargs=None, conversion_functions=None, na_marker=None, + include_all_data_each=False, **pipeline_step_kwargs): super().__init__(**pipeline_step_kwargs) if datatype_default_kwargs is None: @@ -52,6 +53,7 @@ def __init__( data_types=self.data_types, conversion_functions=conversion_functions, na_marker=na_marker, + include_all_data_each=include_all_data_each, ) @abc.abstractmethod @@ -59,7 +61,7 @@ def read_raw_data(self, input_data=None): pass def decode_data(self, raw_data): - self.logger.debug("Created data decoder: %r", self.decoder) + # self.logger.debug("Created data decoder: %r", self.decoder) decoded_data = self.decoder.decode_data(raw_data) return decoded_data @@ -73,20 +75,23 @@ def execute(self, input_data=None): class SensorInputStep(ValueInputStep, metaclass=abc.ABCMeta): def __init__( self, - sensor_module, sensor_class, + sensor_module=None, sensor_args=None, sensor_kwargs=None, cache_sensor_object=False, + binary_decoder=False, **value_input_kwargs): - super().__init__(binary_decoder=False, **value_input_kwargs) + super().__init__(binary_decoder=binary_decoder, **value_input_kwargs) self.sensor_args = () if sensor_args is None else sensor_args self.sensor_kwargs = {} if sensor_kwargs is None else sensor_kwargs self.sensor_object = None self.cache_sensor_object = cache_sensor_object - module_object = importlib.import_module(sensor_module) - self.object_class = getattr(module_object, sensor_class) + if sensor_module: + module_object = importlib.import_module(sensor_module) + sensor_class = getattr(module_object, sensor_class) + self.object_class = sensor_class def init_sensor_object(self, *sensor_args, **sensor_kwargs): if not sensor_args: @@ -111,18 +116,33 @@ def init_sensor_object(self, *sensor_args, **sensor_kwargs): self.sensor_object = sensor_object return sensor_object + def get_sensor_object(self, sensor_object=None): + if sensor_object is not None: + return sensor_object + if self.sensor_object is not None: + return self.sensor_object + + sensor_object = self.init_sensor_object() + return sensor_object + + @abc.abstractmethod + def read_sensor_data(self, sensor_object=None): + pass + + def read_raw_data(self, input_data=None): + raw_data = self.read_sensor_data() + return raw_data + + +class AttributeInputStep(SensorInputStep, metaclass=abc.ABCMeta): @abc.abstractmethod def read_sensor_value(self, sensor_object, data_type): pass def read_sensor_data(self, sensor_object=None): + sensor_object = self.get_sensor_object(sensor_object=sensor_object) if sensor_object is None: - sensor_object = self.sensor_object - - if sensor_object is None: - sensor_object = self.init_sensor_object() - if sensor_object is None: - return None + return None sensor_data = [] for data_type in self.data_types: @@ -131,33 +151,29 @@ def read_sensor_data(self, sensor_object=None): sensor_object=sensor_object, data_type=data_type) except Exception as e: self.logger.error( - "%s getting attribute %s from %s sensor object %s " + "%s on attribute %s from %s sensor object %s " "on step %s: %s", - type(e).__name__, data_type.property_name, type(self), + type(e).__name__, data_type.attribute_name, type(self), type(self.object_class), self.name, e) self.logger.info("Error details:", exc_info=True) data_value = None sensor_data.append(data_value) return sensor_data - def read_raw_data(self, input_data=None): - raw_data = self.read_sensor_data() - return raw_data - -class PropertyInputStep(SensorInputStep): +class PropertyInputStep(AttributeInputStep): def read_sensor_value(self, sensor_object, data_type): if sensor_object is None: sensor_object = self.sensor_object - data_value = getattr(sensor_object, data_type.property_name) + data_value = getattr(sensor_object, data_type.attribute_name) return data_value -class MethodInputStep(SensorInputStep): +class MethodInputStep(AttributeInputStep): def read_sensor_value(self, sensor_object, data_type): if sensor_object is None: sensor_object = self.sensor_object function_kwargs = getattr(data_type, "function_kwargs", {}) - data_value = getattr(sensor_object, data_type.function_name)( + data_value = getattr(sensor_object, data_type.attribute_name)( **function_kwargs) return data_value diff --git a/src/brokkr/pipeline/decode.py b/src/brokkr/pipeline/decode.py index a34bd78..672ff38 100644 --- a/src/brokkr/pipeline/decode.py +++ b/src/brokkr/pipeline/decode.py @@ -27,7 +27,7 @@ ast.BitOr: operator.or_, ast.BitXor: operator.xor, ast.Invert: operator.invert, - ast.RShift: operator.rshift, + ast.LShift: operator.lshift, ast.RShift: operator.rshift, } @@ -165,8 +165,10 @@ def __init__( data_types, na_marker=None, conversion_functions=None, + include_all_data_each=False, ): self.data_types = data_types + self.include_all_data_each = include_all_data_each if conversion_functions is None: conversion_functions = {} conversion_functions = { @@ -192,6 +194,7 @@ def output_na_values(self): return output_data def convert_data(self, raw_data): + # pylint: disable=too-many-branches if not raw_data: output_data = self.output_na_values() LOGGER.debug("No data to convert, returning: %r", output_data) @@ -200,9 +203,12 @@ def convert_data(self, raw_data): error_count = 0 output_data = {} - for data_type, value in zip(self.data_types, raw_data): + for idx, data_type in enumerate(self.data_types): if not data_type.conversion: continue # If this data value should be dropped, ignore it + value = raw_data + if not self.include_all_data_each: + value = value[idx] if value is None: LOGGER.debug("Data value is None decoding data_type %r to %s, " "coercing to NA", @@ -250,10 +256,12 @@ def convert_data(self, raw_data): output_data[data_type.name] = data_value if error_count > 1: - LOGGER.warning("%s additioanl decode errors were suppressed.", + LOGGER.warning("%s additional decode errors were suppressed.", error_count - 1) - LOGGER.debug("Converted data: %r", output_data) + LOGGER.debug("Converted data: {%s}", + ", ".join([f"{key!r}: {value!s}" + for key, value in output_data.items()])) return output_data def decode_data(self, data): From 12bb4a43dbc5d293679e51d65b45b9005e5f3250 Mon Sep 17 00:00:00 2001 From: "C.A.M. Gerlach" Date: Sun, 10 May 2020 18:15:15 -0500 Subject: [PATCH 13/13] Add additional debug logging information for data inputs --- src/brokkr/inputs/adafruitadc.py | 2 +- src/brokkr/inputs/adafruiti2c.py | 6 +++--- src/brokkr/inputs/smbusi2c.py | 9 ++++++++- src/brokkr/outputs/print.py | 2 +- src/brokkr/pipeline/baseinput.py | 19 +++++++++++++++---- src/brokkr/pipeline/decode.py | 24 +++++++++++------------- src/brokkr/utils/output.py | 2 +- 7 files changed, 40 insertions(+), 24 deletions(-) diff --git a/src/brokkr/inputs/adafruitadc.py b/src/brokkr/inputs/adafruitadc.py index 7033340..4203228 100644 --- a/src/brokkr/inputs/adafruitadc.py +++ b/src/brokkr/inputs/adafruitadc.py @@ -51,7 +51,7 @@ def __init__( def read_sensor_data(self, sensor_object=None): with busio.I2C(board.SCL, board.SDA, **self._i2c_kwargs) as i2c: sensor_object = self.init_sensor_object(i2c) - if not sensor_object: + if sensor_object is None: return None channel_object = self._analog_class( sensor_object, **self._adc_kwargs) diff --git a/src/brokkr/inputs/adafruiti2c.py b/src/brokkr/inputs/adafruiti2c.py index c7d38d3..05f6242 100644 --- a/src/brokkr/inputs/adafruiti2c.py +++ b/src/brokkr/inputs/adafruiti2c.py @@ -23,10 +23,10 @@ class AdafruitI2CInput(BaseAdafruitI2CInput): def read_sensor_data(self, sensor_object=None): with busio.I2C(board.SCL, board.SDA, **self._i2c_kwargs) as i2c: sensor_object = self.init_sensor_object(i2c) - if not sensor_object: + if sensor_object is None: return None - raw_data = super().read_sensor_data(sensor_object=sensor_object) - return raw_data + sensor_data = super().read_sensor_data(sensor_object=sensor_object) + return sensor_data class AdafruitPersistantI2CInput(BaseAdafruitI2CInput): diff --git a/src/brokkr/inputs/smbusi2c.py b/src/brokkr/inputs/smbusi2c.py index f0db663..e462386 100644 --- a/src/brokkr/inputs/smbusi2c.py +++ b/src/brokkr/inputs/smbusi2c.py @@ -3,6 +3,7 @@ """ # Standard library imports +import logging from pathlib import Path # Third party imports @@ -18,6 +19,8 @@ I2C_BLOCK_READ_FUNCTION = "read_i2c_block_data" DEFAULT_READ_FUNCTION = I2C_BLOCK_READ_FUNCTION +LOGGER = logging.getLogger(__name__) + class SMBusI2CDevice(brokkr.utils.misc.AutoReprMixin): def __init__(self, bus=None, force=None): @@ -28,6 +31,7 @@ def __init__(self, bus=None, force=None): for n_bus in range(0, MAX_I2C_BUS_N + 1): if Path(f"/dev/i2c-{n_bus}").exists(): bus = n_bus + LOGGER.debug("Found I2C device at bus %s", bus) break else: raise RuntimeError("Could not find I2C any bus device") @@ -38,9 +42,12 @@ def read(self, force=None, read_function=DEFAULT_READ_FUNCTION, **read_kwargs): if force is None: force = self.force + LOGGER.debug("Reading I2C data with function %s at bus %r, kwargs %r", + read_function, self.bus, read_kwargs) with smbus2.SMBus(self.bus, force=self.force) as i2c_bus: buffer = getattr(i2c_bus, read_function)( force=force, **read_kwargs) + LOGGER.debug("Read I2C data %r", buffer) return buffer @@ -77,7 +84,7 @@ def read_sensor_data(self, sensor_object=None): "%s reading data from I2C SMBus device with function %s " "of %s sensor object %s on step %s: %s", type(e).__name__, self._read_function, - type(self), type(self.object_class), self.name, e) + type(self), self.object_class, self.name, e) self.logger.info("Error details:", exc_info=True) sensor_data = None diff --git a/src/brokkr/outputs/print.py b/src/brokkr/outputs/print.py index f66e8e1..b5a0d4d 100644 --- a/src/brokkr/outputs/print.py +++ b/src/brokkr/outputs/print.py @@ -41,7 +41,7 @@ def __init__(self, in_place=True, **print_output_kwargs): super().__init__(in_place=in_place, **print_output_kwargs) def execute(self, input_data=None): - output_data = brokkr.utils.output.format_data(input_data) + output_data = brokkr.utils.output.format_data(input_data) + "\n" if self.in_place and self.ran_once: output_data = ( (CURSOR_UP_CHAR + ERASE_LINE_CHAR) * output_data.count("\n") diff --git a/src/brokkr/pipeline/baseinput.py b/src/brokkr/pipeline/baseinput.py index 4ade76b..7eedb99 100644 --- a/src/brokkr/pipeline/baseinput.py +++ b/src/brokkr/pipeline/baseinput.py @@ -35,7 +35,7 @@ def __init__( except AttributeError: # If data_type isn't already an object try: data_type["name"] - except TypeError: + except TypeError: # If data_types is a list, not a dict data_type_dict = data_types[data_type] data_type_dict["name"] = data_type data_type = data_type_dict @@ -88,7 +88,7 @@ def __init__( self.sensor_object = None self.cache_sensor_object = cache_sensor_object - if sensor_module: + if sensor_module is not None: module_object = importlib.import_module(sensor_module) sensor_class = getattr(module_object, sensor_class) self.object_class = sensor_class @@ -99,21 +99,28 @@ def init_sensor_object(self, *sensor_args, **sensor_kwargs): if not sensor_kwargs: sensor_kwargs = self.sensor_kwargs + self.logger.debug( + "Initializing sensor object %s with args %r, kwargs %s", + self.object_class, sensor_args, sensor_kwargs) try: sensor_object = self.object_class( *sensor_args, **sensor_kwargs) except Exception as e: self.logger.error( "%s initializing %s sensor object %s on step %s: %s", - type(e).__name__, type(self), type(self.object_class), + type(e).__name__, type(self), self.object_class, self.name, e) self.logger.info("Error details:", exc_info=True) self.logger.info("Sensor args: %r | Sensor kwargs: %r:", sensor_args, sensor_kwargs) sensor_object = None + self.logger.debug( + "Initialized sensor object %s to %r", + self.object_class, sensor_object) if self.cache_sensor_object: self.sensor_object = sensor_object + self.logger.debug("Cached sensor object %s", self.object_class) return sensor_object def get_sensor_object(self, sensor_object=None): @@ -154,9 +161,13 @@ def read_sensor_data(self, sensor_object=None): "%s on attribute %s from %s sensor object %s " "on step %s: %s", type(e).__name__, data_type.attribute_name, type(self), - type(self.object_class), self.name, e) + self.object_class, self.name, e) self.logger.info("Error details:", exc_info=True) data_value = None + else: + self.logger.debug( + "Read value %r from attribute %s of sensor %s", + data_value, data_type.attribute_name, self.object_class) sensor_data.append(data_value) return sensor_data diff --git a/src/brokkr/pipeline/decode.py b/src/brokkr/pipeline/decode.py index 672ff38..5103b77 100644 --- a/src/brokkr/pipeline/decode.py +++ b/src/brokkr/pipeline/decode.py @@ -16,6 +16,7 @@ # Local imports import brokkr.pipeline.datavalue import brokkr.utils.misc +import brokkr.utils.output NA_MARKER_DEFAULT = "NA" @@ -194,12 +195,6 @@ def output_na_values(self): return output_data def convert_data(self, raw_data): - # pylint: disable=too-many-branches - if not raw_data: - output_data = self.output_na_values() - LOGGER.debug("No data to convert, returning: %r", output_data) - return output_data - error_count = 0 output_data = {} @@ -210,9 +205,10 @@ def convert_data(self, raw_data): if not self.include_all_data_each: value = value[idx] if value is None: - LOGGER.debug("Data value is None decoding data_type %r to %s, " - "coercing to NA", - data_type.name, data_type.conversion) + LOGGER.debug("Data value is None decoding data_type %s to %s, " + "coercing to NA value %r", + data_type.name, data_type.conversion, + self.output_na_value(data_type)) output_data[data_type.name] = self.output_na_value(data_type) continue try: @@ -259,15 +255,17 @@ def convert_data(self, raw_data): LOGGER.warning("%s additional decode errors were suppressed.", error_count - 1) - LOGGER.debug("Converted data: {%s}", - ", ".join([f"{key!r}: {value!s}" - for key, value in output_data.items()])) + LOGGER.debug("Converted data: {%s}", brokkr.utils.output.format_data( + data=output_data, seperator=", ", include_raw=True)) return output_data def decode_data(self, data): if data is None: - LOGGER.debug("No data to decode") output_data = self.output_na_values() + LOGGER.debug( + "No data to decode, returning NAs: %r", + brokkr.utils.output.format_data( + data=output_data, seperator=", ", include_raw=False)) else: output_data = self.convert_data(data) return output_data diff --git a/src/brokkr/utils/output.py b/src/brokkr/utils/output.py index 548db51..85ce6a1 100644 --- a/src/brokkr/utils/output.py +++ b/src/brokkr/utils/output.py @@ -49,7 +49,7 @@ def format_data(data=None, seperator="\n", include_raw=False): data_item = " ".join(data_componets) output_data_list.append(data_item) - formatted_data = seperator.join(output_data_list) + "\n" + formatted_data = seperator.join(output_data_list) return formatted_data