diff --git a/.frontmatter/database/taxonomyDb.json b/.frontmatter/database/taxonomyDb.json new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/.frontmatter/database/taxonomyDb.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/DOCS/preparing_firmware.md b/DOCS/preparing_firmware.md new file mode 100644 index 0000000..2718b90 --- /dev/null +++ b/DOCS/preparing_firmware.md @@ -0,0 +1,4 @@ +# Preparing Firmware to run on the Pico + +Instead of simply uploading the micropython code to the pico, it can first be "compiled" using built in tools in micropython. This can reduce the runtime demands placed on the pico. + diff --git a/DOCS/register_new_analog_sensor.md b/DOCS/register_new_analog_sensor.md index f134bf0..de72ab9 100644 --- a/DOCS/register_new_analog_sensor.md +++ b/DOCS/register_new_analog_sensor.md @@ -1,6 +1,6 @@ -# Register New Analog Sensor +## Register New Analog Sensor -Analog sensors can be registered by passing a dictionary and base sensor instance to the `registre_analog_function_to_sensor` function found in the `registration` module. +Analog sensors can be registered by passing a dictionary and base sensor instance to the `register_analog_function_to_sensor` function found in the `registration` module. Because the analog sensors rely on directly taking a reading from the end-point sensor, the only things we need to tell our board about them is what we would diff --git a/analog_example.py b/analog_example.py new file mode 100644 index 0000000..090e3c2 --- /dev/null +++ b/analog_example.py @@ -0,0 +1,7 @@ +from sensor import Sensor + +if __name__ == "__main__": + sensor = Sensor(sensor_name="soil_moisture_demo_1") + sensor.register_analog_sensor_function("soil_moisture", 28) + + sensor.run() \ No newline at end of file diff --git a/bme280.py b/bme280.py new file mode 100644 index 0000000..fe2f79f --- /dev/null +++ b/bme280.py @@ -0,0 +1,297 @@ +# modified from: https://github.com/RuiSantosdotme/ESP-MicroPython/blob/master/code/WiFi/HTTP_Client_IFTTT_BME280/BME280.py +import time + +# BME280 default address. +BME280_I2CADDR = 0x77 + +# Operating Modes +BME280_OSAMPLE_1 = 1 +BME280_OSAMPLE_2 = 2 +BME280_OSAMPLE_4 = 3 +BME280_OSAMPLE_8 = 4 +BME280_OSAMPLE_16 = 5 + +# BME280 Registers + +BME280_REGISTER_DIG_T1 = 0x88 # Trimming parameter registers +BME280_REGISTER_DIG_T2 = 0x8A +BME280_REGISTER_DIG_T3 = 0x8C + +BME280_REGISTER_DIG_P1 = 0x8E +BME280_REGISTER_DIG_P2 = 0x90 +BME280_REGISTER_DIG_P3 = 0x92 +BME280_REGISTER_DIG_P4 = 0x94 +BME280_REGISTER_DIG_P5 = 0x96 +BME280_REGISTER_DIG_P6 = 0x98 +BME280_REGISTER_DIG_P7 = 0x9A +BME280_REGISTER_DIG_P8 = 0x9C +BME280_REGISTER_DIG_P9 = 0x9E + +BME280_REGISTER_DIG_H1 = 0xA1 +BME280_REGISTER_DIG_H2 = 0xE1 +BME280_REGISTER_DIG_H3 = 0xE3 +BME280_REGISTER_DIG_H4 = 0xE4 +BME280_REGISTER_DIG_H5 = 0xE5 +BME280_REGISTER_DIG_H6 = 0xE6 +BME280_REGISTER_DIG_H7 = 0xE7 + +BME280_REGISTER_CHIPID = 0xD0 +BME280_REGISTER_VERSION = 0xD1 +BME280_REGISTER_SOFTRESET = 0xE0 + +BME280_REGISTER_CONTROL_HUM = 0xF2 +BME280_REGISTER_CONTROL = 0xF4 +BME280_REGISTER_CONFIG = 0xF5 +BME280_REGISTER_PRESSURE_DATA = 0xF7 +BME280_REGISTER_TEMP_DATA = 0xFA +BME280_REGISTER_HUMIDITY_DATA = 0xFD + + +class Device: + """Class for communicating with an I2C device. + + Allows reading and writing 8-bit, 16-bit, and byte array values to + registers on the device.""" + + def __init__(self, address, i2c): + """Create an instance of the I2C device at the specified address using + the specified I2C interface object.""" + self._address = address + self._i2c = i2c + + def writeRaw8(self, value): + """Write an 8-bit value on the bus (without register).""" + value = value & 0xFF + self._i2c.writeto(self._address, value) + + def write8(self, register, value): + """Write an 8-bit value to the specified register.""" + b = bytearray(1) + b[0] = value & 0xFF + self._i2c.writeto_mem(self._address, register, b) + + def write16(self, register, value): + """Write a 16-bit value to the specified register.""" + value = value & 0xFFFF + b = bytearray(2) + b[0] = value & 0xFF + b[1] = (value >> 8) & 0xFF + self.i2c.writeto_mem(self._address, register, value) + + def readRaw8(self): + """Read an 8-bit value on the bus (without register).""" + return int.from_bytes(self._i2c.readfrom(self._address, 1), 'little') \ + & 0xFF + + def readU8(self, register): + """Read an unsigned byte from the specified register.""" + return int.from_bytes( + self._i2c.readfrom_mem(self._address, register, 1), 'little') \ + & 0xFF + + def readS8(self, register): + """Read a signed byte from the specified register.""" + result = self.readU8(register) + if result > 127: + result -= 256 + return result + + def readU16(self, register, little_endian=True): + """Read an unsigned 16-bit value from the specified register, with the + specified endianness (default little endian, or least significant byte + first).""" + result = int.from_bytes( + self._i2c.readfrom_mem(self._address, register, 2), 'little') \ + & 0xFFFF + if not little_endian: + result = ((result << 8) & 0xFF00) + (result >> 8) + return result + + def readS16(self, register, little_endian=True): + """Read a signed 16-bit value from the specified register, with the + specified endianness (default little endian, or least significant byte + first).""" + result = self.readU16(register, little_endian) + if result > 32767: + result -= 65536 + return result + + def readU16LE(self, register): + """Read an unsigned 16-bit value from the specified register, in little + endian byte order.""" + return self.readU16(register, little_endian=True) + + def readU16BE(self, register): + """Read an unsigned 16-bit value from the specified register, in big + endian byte order.""" + return self.readU16(register, little_endian=False) + + def readS16LE(self, register): + """Read a signed 16-bit value from the specified register, in little + endian byte order.""" + return self.readS16(register, little_endian=True) + + def readS16BE(self, register): + """Read a signed 16-bit value from the specified register, in big + endian byte order.""" + return self.readS16(register, little_endian=False) + + +class BME280: + def __init__(self, mode=BME280_OSAMPLE_1, address=BME280_I2CADDR, i2c=None, + **kwargs): + # Check that mode is valid. + if mode not in [BME280_OSAMPLE_1, BME280_OSAMPLE_2, BME280_OSAMPLE_4, + BME280_OSAMPLE_8, BME280_OSAMPLE_16]: + raise ValueError( + 'Unexpected mode value {0}. Set mode to one of ' + 'BME280_ULTRALOWPOWER, BME280_STANDARD, BME280_HIGHRES, or ' + 'BME280_ULTRAHIGHRES'.format(mode)) + self._mode = mode + # Create I2C device. + if i2c is None: + raise ValueError('An I2C object is required.') + self._device = Device(address, i2c) + # Load calibration values. + self._load_calibration() + self._device.write8(BME280_REGISTER_CONTROL, 0x3F) + self.t_fine = 0 + + def _load_calibration(self): + self.dig_T1 = self._device.readU16LE(BME280_REGISTER_DIG_T1) + self.dig_T2 = self._device.readS16LE(BME280_REGISTER_DIG_T2) + self.dig_T3 = self._device.readS16LE(BME280_REGISTER_DIG_T3) + + self.dig_P1 = self._device.readU16LE(BME280_REGISTER_DIG_P1) + self.dig_P2 = self._device.readS16LE(BME280_REGISTER_DIG_P2) + self.dig_P3 = self._device.readS16LE(BME280_REGISTER_DIG_P3) + self.dig_P4 = self._device.readS16LE(BME280_REGISTER_DIG_P4) + self.dig_P5 = self._device.readS16LE(BME280_REGISTER_DIG_P5) + self.dig_P6 = self._device.readS16LE(BME280_REGISTER_DIG_P6) + self.dig_P7 = self._device.readS16LE(BME280_REGISTER_DIG_P7) + self.dig_P8 = self._device.readS16LE(BME280_REGISTER_DIG_P8) + self.dig_P9 = self._device.readS16LE(BME280_REGISTER_DIG_P9) + + self.dig_H1 = self._device.readU8(BME280_REGISTER_DIG_H1) + self.dig_H2 = self._device.readS16LE(BME280_REGISTER_DIG_H2) + self.dig_H3 = self._device.readU8(BME280_REGISTER_DIG_H3) + self.dig_H6 = self._device.readS8(BME280_REGISTER_DIG_H7) + + h4 = self._device.readS8(BME280_REGISTER_DIG_H4) + h4 = (h4 << 24) >> 20 + self.dig_H4 = h4 | (self._device.readU8(BME280_REGISTER_DIG_H5) & 0x0F) + + h5 = self._device.readS8(BME280_REGISTER_DIG_H6) + h5 = (h5 << 24) >> 20 + self.dig_H5 = h5 | ( + self._device.readU8(BME280_REGISTER_DIG_H5) >> 4 & 0x0F) + + def read_raw_temp(self): + """Reads the raw (uncompensated) temperature from the sensor.""" + meas = self._mode + self._device.write8(BME280_REGISTER_CONTROL_HUM, meas) + meas = self._mode << 5 | self._mode << 2 | 1 + self._device.write8(BME280_REGISTER_CONTROL, meas) + sleep_time = 1250 + 2300 * (1 << self._mode) + + sleep_time = sleep_time + 2300 * (1 << self._mode) + 575 + sleep_time = sleep_time + 2300 * (1 << self._mode) + 575 + time.sleep_us(sleep_time) # Wait the required time + msb = self._device.readU8(BME280_REGISTER_TEMP_DATA) + lsb = self._device.readU8(BME280_REGISTER_TEMP_DATA + 1) + xlsb = self._device.readU8(BME280_REGISTER_TEMP_DATA + 2) + raw = ((msb << 16) | (lsb << 8) | xlsb) >> 4 + return raw + + def read_raw_pressure(self): + """Reads the raw (uncompensated) pressure level from the sensor.""" + """Assumes that the temperature has already been read """ + """i.e. that enough delay has been provided""" + msb = self._device.readU8(BME280_REGISTER_PRESSURE_DATA) + lsb = self._device.readU8(BME280_REGISTER_PRESSURE_DATA + 1) + xlsb = self._device.readU8(BME280_REGISTER_PRESSURE_DATA + 2) + raw = ((msb << 16) | (lsb << 8) | xlsb) >> 4 + return raw + + def read_raw_humidity(self): + """Assumes that the temperature has already been read """ + """i.e. that enough delay has been provided""" + msb = self._device.readU8(BME280_REGISTER_HUMIDITY_DATA) + lsb = self._device.readU8(BME280_REGISTER_HUMIDITY_DATA + 1) + raw = (msb << 8) | lsb + return raw + + def read_temperature(self): + """Get the compensated temperature in 0.01 of a degree celsius.""" + adc = self.read_raw_temp() + var1 = ((adc >> 3) - (self.dig_T1 << 1)) * (self.dig_T2 >> 11) + var2 = (( + (((adc >> 4) - self.dig_T1) * ((adc >> 4) - self.dig_T1)) >> 12) * + self.dig_T3) >> 14 + self.t_fine = var1 + var2 + raw_temp = (self.t_fine * 5 + 128) >> 8 + temp_int = raw_temp // 100 + temp_dec = raw_temp - temp_int * 100 + return float("{0}.{1}".format(temp_int, temp_dec)) + + def read_pressure(self): + """Gets the compensated pressure in hPascals.""" + adc = self.read_raw_pressure() + var1 = self.t_fine - 128000 + var2 = var1 * var1 * self.dig_P6 + var2 = var2 + ((var1 * self.dig_P5) << 17) + var2 = var2 + (self.dig_P4 << 35) + var1 = (((var1 * var1 * self.dig_P3) >> 8) + + ((var1 * self.dig_P2) >> 12)) + var1 = (((1 << 47) + var1) * self.dig_P1) >> 33 + if var1 == 0: + return 0 + p = 1048576 - adc + p = (((p << 31) - var2) * 3125) // var1 + var1 = (self.dig_P9 * (p >> 13) * (p >> 13)) >> 25 + var2 = (self.dig_P8 * p) >> 19 + raw_pressure = ((p + var1 + var2) >> 8) + (self.dig_P7 << 4) + p = raw_pressure // 256 + p_int = p // 100 + p_dec = p - p_int * 100 + return float("{0}.{1}".format(p_int, p_dec)) + + def read_humidity(self): + adc = self.read_raw_humidity() + # print 'Raw humidity = {0:d}'.format (adc) + h = self.t_fine - 76800 + h = (((((adc << 14) - (self.dig_H4 << 20) - (self.dig_H5 * h)) + + 16384) >> 15) * (((((((h * self.dig_H6) >> 10) * (((h * + self.dig_H3) >> 11) + 32768)) >> 10) + 2097152) * + self.dig_H2 + 8192) >> 14)) + h = h - (((((h >> 15) * (h >> 15)) >> 7) * self.dig_H1) >> 4) + h = 0 if h < 0 else h + h = 419430400 if h > 419430400 else h + raw_humidity = h >> 12 + h_int = raw_humidity // 1024 + h_dec = raw_humidity * 100 // 1024 - h_int * 100 + return float("{0}.{1}".format(h_int, h_dec)) + + @property + def temperature(self): + "Return the temperature in degrees Celcius." + t = self.read_temperature() + ti = t // 100 + td = t - ti * 100 + return "{}.{:02d}C".format(ti, td) + + @property + def pressure(self): + "Return the temperature in hPa." + p = self.read_pressure() // 256 + pi = p // 100 + pd = p - pi * 100 + return "{}.{:02d}hPa".format(pi, pd) + + @property + def humidity(self): + "Return the humidity in percent." + h = self.read_humidity() + hi = h // 1024 + hd = h * 100 // 1024 - hi * 100 + return "{}.{:02d}%".format(hi, hd) diff --git a/environment.yml b/environment.yml deleted file mode 100644 index f597439..0000000 --- a/environment.yml +++ /dev/null @@ -1,7 +0,0 @@ -name: sensor_firmware -channels: - - conda-forge -dependencies: - - python=3.11 - - pydocstyle - - pyserial diff --git a/frontmatter.json b/frontmatter.json new file mode 100644 index 0000000..561c5b4 --- /dev/null +++ b/frontmatter.json @@ -0,0 +1,3 @@ +{ + "$schema": "https://frontmatter.codes/frontmatter.schema.json" +} \ No newline at end of file diff --git a/lib/i2c.py b/lib/i2c.py new file mode 100644 index 0000000..8ce160a --- /dev/null +++ b/lib/i2c.py @@ -0,0 +1,93 @@ + +#TODO: still in development + +if I2CSensor: + self.i2c_bus = machine.SoftI2C(sda=machine.Pin(0), scl=machine.Pin(1)) +else: + self.i2c_bus = None + +def register_i2c_sensor_function(self, name, func_name, address, cmd, cmd_type, num_bytes=0): + """Define data to read from i2c sensor + + name: name of the measurement + address: i2c address of the sensor + cmd: command to send to the sensor to initiate reading(will be a byte encoded integer) + num_bytes: number of bytes to read from the sensor + hash_func: function to hash the cmd to a byte encoded integer. If None, cmd will be sent as is. This is the default behavior, but some sensors require a hash function to be used, mostly those based on sensors used in safety critical applications in commercial applications, such as the air quality sensor. + """ + target_dict = dict() + + if cmd_type == "init": + target_dict = self.init_functions + elif cmd_type == "measure": + target_dict = self.measurement_functions + else: + raise ValueError("cmd_type must be one of: init, measure") + + if hash_func is None: + target_dict[name] = (lambda : self.write_i2c(address, cmd)) + else: + target_dict[name] = self.write_i2c(address, hash_func(cmd)) + + print(target_dict) + if cmd_type == 'measure': + return self.read_i2c(address, num_bytes) + + +def _i2c_function_(self, address, cmd, num_bytes): + self.write_i2c(address, cmd) + return self.read_i2c(address, num_bytes) + +def register_i2c_sensor_measurement_function(self, name, address, cmd, num_bytes): + self.measurement_functions[name] = self._i2c_function_(address=address, cmd=cmd, num_bytes=num_bytes) + +### SENSOR CLASS METHODS + ### TODO: implement i2c functions and constructor + def read_i2c(self, address, num_bytes): + if self.i2c_bus is None: + self.i2c_bus = machine.SoftI2C(sda=machine.Pin(0), scl=machine.Pin(1)) + return self.i2c_bus.readfrom(address, num_bytes) + + @try_until_i2c + def write_i2c(self, address, data): + if self.i2c_bus is None: + self.i2c_bus = machine.SoftI2C(sda=machine.Pin(0), scl=machine.Pin(1)) + self.i2c_bus.writeto(address, data) + time.sleep_ms(100) + +#### dev/testing code) + # INIT_AIR_QUALITY = b'\x20\x03' + # MEASURE_AIR_QUALITY = bytearray([0x20, 0x08]) + # MEASURE_RAW_SIGNALS = bytearray([0x20, 0x50]) + # sensor.register_i2c_sensor_measurement_function(name = "air_quality_init", address=0x58, cmd=INIT_AIR_QUALITY, num_bytes=0) + # sensor.register_i2c_sensor_measurement_function("measure_air_quality", + # address=0x58, + # cmd=MEASURE_AIR_QUALITY, + # num_bytes=3 + # ) + # @sensor.register_measurement_function + # def measure_air_quality(cls): + # cls.write_i2c(0x58, MEASURE_AIR_QUALITY) + # return cls.read_i2c(0x58, 3) +# + + # INIT_AIR_QUALITY = bytearray([0x20, 0x03] + def register_i2c_measurement_function(name, address, cmd, num_bytes): + def wrapper(*args, **kwargs): + self.name = kwargs.get('name') + self.write_i2c(address, cmd) + self.read_i2c(address, num_bytes) + out = func(*args, **kwargs) + return out + return wrapper + # self.measurement_functions[f'{func.__name__}'] = wrapper + +# generic measurment function decorator + def register_measurement_function(self, func): + def wrapper(*args, **kwargs): + print(f'registering {func.__name__}') + out = func(*args, **kwargs) + print(f'"{func.__name__}" registered to "{self.sensor_name}"') + return out + # return wrapper + self.measurement_functions[f'{func.__name__}'] = wrapper \ No newline at end of file diff --git a/lib/registration.py b/lib/registration.py index 378df11..c14a3b5 100644 --- a/lib/registration.py +++ b/lib/registration.py @@ -1,5 +1,8 @@ def register_analog_function_to_sensor(sensor, def_dict): for k, v in def_dict.items(): - raise("registered: {} to {}".format(k, v)) sensor.register_analog_sensor_function(name=k, pin=v) - \ No newline at end of file + +# +# def register_I2C_function_to_sensor(sensor, def_dict): + # for k, v in def_dict.items(): + # sensor.register_digital_sensor_function(name=k, pin=v) \ No newline at end of file diff --git a/main.py b/main.py new file mode 100644 index 0000000..eb138a8 --- /dev/null +++ b/main.py @@ -0,0 +1,23 @@ +from sensor import Sensor +from bme280 import BME280 +from machine import WDT + +# sensor = Sensor(sensor_name="soil_moisture_demo_1") +# sensor.register_analog_sensor_function("soil_moisture", 28) + +# I2C sensor example +sensor = Sensor(sensor_name="atmospheric", + topic='sensor_data/atmospheric', + I2CSensor=True, + wdt=WDT(timeout=7000)) + +environ_sensor = BME280(i2c=sensor.i2c_bus) +sensor.register_i2c_sensor_function('temperature', + environ_sensor.read_temperature) +sensor.register_i2c_sensor_function('humidity', + environ_sensor.read_humidity) +sensor.register_i2c_sensor_function('pressure', + environ_sensor.read_pressure) + +if __name__ == "__main__": + sensor.run() \ No newline at end of file diff --git a/mains_and_manifests/atmospheric_main.py b/mains_and_manifests/atmospheric_main.py new file mode 100644 index 0000000..eb138a8 --- /dev/null +++ b/mains_and_manifests/atmospheric_main.py @@ -0,0 +1,23 @@ +from sensor import Sensor +from bme280 import BME280 +from machine import WDT + +# sensor = Sensor(sensor_name="soil_moisture_demo_1") +# sensor.register_analog_sensor_function("soil_moisture", 28) + +# I2C sensor example +sensor = Sensor(sensor_name="atmospheric", + topic='sensor_data/atmospheric', + I2CSensor=True, + wdt=WDT(timeout=7000)) + +environ_sensor = BME280(i2c=sensor.i2c_bus) +sensor.register_i2c_sensor_function('temperature', + environ_sensor.read_temperature) +sensor.register_i2c_sensor_function('humidity', + environ_sensor.read_humidity) +sensor.register_i2c_sensor_function('pressure', + environ_sensor.read_pressure) + +if __name__ == "__main__": + sensor.run() \ No newline at end of file diff --git a/mains_and_manifests/atmospheric_manifest.py b/mains_and_manifests/atmospheric_manifest.py new file mode 100644 index 0000000..8a866b1 --- /dev/null +++ b/mains_and_manifests/atmospheric_manifest.py @@ -0,0 +1,2 @@ +require('mqtt.simple') +package(./pico_bug) \ No newline at end of file diff --git a/sensor.py b/sensor.py index fe5a128..665bfd4 100644 --- a/sensor.py +++ b/sensor.py @@ -1,6 +1,6 @@ import time import machine -from lib.util import connect_network +from util import connect_network, _i2c_function_ # TODO: add a way to register a fun try: @@ -14,7 +14,7 @@ import json class Sensor: - def __init__(self, sensor_name, topic='sensor_data', indicator_pin="LED", reporting_interval_sec=5, I2CSensor=False) -> None: + def __init__(self, sensor_name, topic='sensor_data', indicator_pin="LED", reporting_interval_sec=5, I2CSensor=False, wdt=None) -> None: self.sensor_name = sensor_name self.status = "initializing" self.network = None @@ -25,6 +25,13 @@ def __init__(self, sensor_name, topic='sensor_data', indicator_pin="LED", report self.indicator_pin = machine.Pin(indicator_pin, machine.Pin.OUT) self.reporting_interval_sec = reporting_interval_sec self.pins = set() + self.wdt = wdt + + # setup i2c bus + if I2CSensor: + self.i2c_bus = machine.SoftI2C(sda=machine.Pin(0), scl=machine.Pin(1)) + else: + self.i2c_bus = None # TODO: turn back on try: @@ -57,12 +64,22 @@ def register_analog_sensor_function(self, name, pin): self.measurement_functions[name] = machine.ADC(pin).read_u16 print("{}: {}".format(name, self.measurement_functions[name]())) + + def register_i2c_sensor_function(self, measurement_name, func): + if self.i2c_bus is None: + self.i2c_bus = machine.SoftI2C(sda=machine.Pin(0), + scl=machine.Pin(1)) + print("I2C bus initialized") + + self.measurement_functions[measurement_name] = func + def measurements(self): measurements = json.dumps( { "sensor": self.sensor_name, "data": { - key: value() for key, value in self.measurement_functions.items() + key: value() for key, value in + self.measurement_functions.items() }, } ) @@ -80,17 +97,11 @@ def publish(self): self.indicator_pin.off() time.sleep(self.reporting_interval_sec) - def run(self): while True: for name, func in self.measurement_functions.items(): print(name, func()) self.publish() time.sleep(self.reporting_interval_sec) + self.wdt.feed() - -if __name__ == "__main__": - sensor = Sensor(sensor_name="soil_moisture_demo_1") - sensor.register_analog_sensor_function("soil_moisture", 28) - - sensor.run() diff --git a/tests/test_registration.py b/tests/test_registration.py new file mode 100644 index 0000000..a736825 --- /dev/null +++ b/tests/test_registration.py @@ -0,0 +1,12 @@ +from sensor import Sensor +from lib.registration import register_analog_function_to_sensor +import unittest + +class TestRegistration(unittest.TestCase): + def setUp(self): + self.sensor = Sensor(sensor_name="registration_test") + + def test_register_analog_sensor(self): + register_analog_function_to_sensor(self.sensor, {"soil_moisture": 28}) + self.assertEqual(self.sensor.analog_sensor_functions["soil_moisture"], 28) + \ No newline at end of file diff --git a/lib/util.py b/util.py similarity index 99% rename from lib/util.py rename to util.py index faf9207..56746ef 100644 --- a/lib/util.py +++ b/util.py @@ -56,6 +56,8 @@ def crc8(msg): crc &= 0xff final = [crc ^ 0x00] print(final) + + # return str(final) # if __name__ == '__main__':