From a1f0feb5af65a0f202faa60c2272ae4682091ccb Mon Sep 17 00:00:00 2001 From: Daniele Date: Wed, 28 Mar 2018 17:58:08 +0200 Subject: [PATCH 1/5] LoRa node first commit --- boot.py | 10 +- config.json | 36 ++++-- lib/LIS2HH12.py | 146 ----------------------- lib/LTR329ALS01.py | 70 ----------- lib/MPL3115A2.py | 119 ------------------ lib/SI7006A20.py | 32 ----- lib/__init__.py | 0 lib/bh1750fvi.py | 20 ++++ lib/bme280.py | 292 +++++++++++++++++++++++++++++++++++++++++++++ lib/deepsleep.py | 188 +++++++++++++++++++++++++++++ lib/loranode.py | 94 +++++++++++++++ lib/onewire.py | 8 +- lib/pycoproc.py | 275 ------------------------------------------ lib/pysense.py | 8 -- lib/sensors.py | 169 ++++++++++++++++++++++++++ lib/sensors_old.py | 79 ++++++++++++ main.py | 117 +++++++++++------- pymakr.conf | 10 ++ sensors.py | 94 --------------- wifi.py | 10 +- 20 files changed, 964 insertions(+), 813 deletions(-) delete mode 100644 lib/LIS2HH12.py delete mode 100644 lib/LTR329ALS01.py delete mode 100644 lib/MPL3115A2.py delete mode 100644 lib/SI7006A20.py create mode 100644 lib/__init__.py create mode 100644 lib/bh1750fvi.py create mode 100644 lib/bme280.py create mode 100644 lib/deepsleep.py create mode 100644 lib/loranode.py delete mode 100644 lib/pycoproc.py delete mode 100644 lib/pysense.py create mode 100644 lib/sensors.py create mode 100644 lib/sensors_old.py create mode 100644 pymakr.conf delete mode 100644 sensors.py diff --git a/boot.py b/boot.py index cbff5c0..54c5ff0 100644 --- a/boot.py +++ b/boot.py @@ -4,16 +4,16 @@ import time print("\n\nBooting Albis Sensor") -pycom.heartbeat(False) -if debug: - pycom.rgbled(0xff0000) -time.sleep_ms(500) with open("config.json", 'r') as cf: conf = ujson.loads(cf.read()) + pycom.heartbeat(False) + if conf['debug']: + pycom.rgbled(0xff0000) + time.sleep_ms(500) if conf['wifi'] is True: import wifi - wifi.connect(conf['config']['wifi']) + wifi.connect(conf) print("Connection done.") machine.main('main.py') diff --git a/config.json b/config.json index 58473f7..374a418 100644 --- a/config.json +++ b/config.json @@ -1,14 +1,32 @@ { - "sensorid": "00caa13a17df11e881ac0800273cbaca", + "sensorid": "bdd27ad4276e11e8817a0800273cbaca", "server": "http://istsos.org/istsos/wa/istsos/services/albis/operations/fastinsert", "debug": true, - "wifi": true, - "read_cnt": 5, - "deepsleep_seconds": 1, + "wifi": false, + "lora": true, + "board": "shield", + "read_cnt": 2, + "deepsleep_seconds": 10, + "type": { + "pysense": { + "temp1": "P22", + "temp2": "P21" + }, + "shield": { + "temp1": "P22", + "temp2": "P21", + "scl": "P9", + "sda": "P10" + } + }, "config": { "wifi": { "ssid": "CBMST", - "password": "xxxxxxxxxxxxxxxxxxxxxxx" + "password": "xxxxxxxxxxxxxxxxx" + }, + "lora": { + "key": "notsuchsecretkey", + "deviceId": 100 } }, "observations": [ @@ -21,12 +39,8 @@ "internal:pressure" ], [ - "urn:ogc:def:parameter:x-istsos:1.0:internal:lux:blue", - "internal:lux:blue" - ], - [ - "urn:ogc:def:parameter:x-istsos:1.0:internal:lux:red", - "internal:lux:red" + "urn:ogc:def:parameter:x-istsos:1.0:internal:lux", + "internal:lux" ], [ "urn:ogc:def:parameter:x-istsos:1.0:external:wall:temperature", diff --git a/lib/LIS2HH12.py b/lib/LIS2HH12.py deleted file mode 100644 index cc421f3..0000000 --- a/lib/LIS2HH12.py +++ /dev/null @@ -1,146 +0,0 @@ -import math -import time -import struct -from machine import Pin - - -FULL_SCALE_2G = const(0) -FULL_SCALE_4G = const(2) -FULL_SCALE_8G = const(3) - -ODR_POWER_DOWN = const(0) -ODR_10_HZ = const(1) -ODR_50_HZ = const(2) -ODR_100_HZ = const(3) -ODR_200_HZ = const(4) -ODR_400_HZ = const(5) -ODR_800_HZ = const(6) - -ACC_G_DIV = 1000 * 65536 - -class LIS2HH12: - - ACC_I2CADDR = const(30) - - PRODUCTID_REG = const(0x0F) - CTRL1_REG = const(0x20) - CTRL2_REG = const(0x21) - CTRL3_REG = const(0x22) - CTRL4_REG = const(0x23) - CTRL5_REG = const(0x24) - ACC_X_L_REG = const(0x28) - ACC_X_H_REG = const(0x29) - ACC_Y_L_REG = const(0x2A) - ACC_Y_H_REG = const(0x2B) - ACC_Z_L_REG = const(0x2C) - ACC_Z_H_REG = const(0x2D) - ACT_THS = const(0x1E) - ACT_DUR = const(0x1F) - - def __init__(self, pysense = None, sda = 'P22', scl = 'P21'): - if pysense is not None: - self.i2c = pysense.i2c - else: - from machine import I2C - self.i2c = I2C(0, mode=I2C.MASTER, pins=(sda, scl)) - - self.reg = bytearray(1) - self.odr = 0 - self.full_scale = 0 - self.x = 0 - self.y = 0 - self.z = 0 - self.int_pin = None - self.act_dur = 0 - self.debounced = False - - self.scales = {FULL_SCALE_2G: 4000, FULL_SCALE_4G: 8000, FULL_SCALE_8G: 16000} - self.odrs = [0, 10, 50, 100, 200, 400, 800] - - whoami = self.i2c.readfrom_mem(ACC_I2CADDR , PRODUCTID_REG, 1) - if (whoami[0] != 0x41): - raise ValueError("LIS2HH12 not found") - - # enable acceleration readings at 50Hz - self.set_odr(ODR_50_HZ) - - # change the full-scale to 4g - self.set_full_scale(FULL_SCALE_4G) - - # set the interrupt pin as active low and open drain - self.i2c.readfrom_mem_into(ACC_I2CADDR , CTRL5_REG, self.reg) - self.reg[0] |= 0b00000011 - self.i2c.writeto_mem(ACC_I2CADDR , CTRL5_REG, self.reg) - - # make a first read - self.acceleration() - - def acceleration(self): - x = self.i2c.readfrom_mem(ACC_I2CADDR , ACC_X_L_REG, 2) - self.x = struct.unpack('> 6) & 0x03) + ((OUT_P_LSB[0] >> 4) & 0x03) / 4.0) - - def altitude(self): - if self.mode == PRESSURE: - raise MPL3115A2exception("Incorrect Measurement Mode MPL3115A2") - - OUT_P_MSB = self.i2c.readfrom_mem(MPL3115_I2CADDR, MPL3115_PRESSURE_DATA_MSB,1) - OUT_P_CSB = self.i2c.readfrom_mem(MPL3115_I2CADDR, MPL3115_PRESSURE_DATA_CSB,1) - OUT_P_LSB = self.i2c.readfrom_mem(MPL3115_I2CADDR, MPL3115_PRESSURE_DATA_LSB,1) - - alt_int = (OUT_P_MSB[0] << 8) + (OUT_P_CSB[0]) - alt_frac = ((OUT_P_LSB[0] >> 4) & 0x0F) - - if alt_int > 32767: - alt_int -= 65536 - - return float(alt_int + alt_frac / 4.0) - - def temperature(self): - OUT_T_MSB = self.i2c.readfrom_mem(MPL3115_I2CADDR, MPL3115_TEMP_DATA_MSB,1) - OUT_T_LSB = self.i2c.readfrom_mem(MPL3115_I2CADDR, MPL3115_TEMP_DATA_LSB,1) - - temp_int = OUT_T_MSB[0] - temp_frac = OUT_T_LSB[0] - - if temp_int > 127: - temp_int -= 256 - - return float(temp_int + temp_frac / 256.0) diff --git a/lib/SI7006A20.py b/lib/SI7006A20.py deleted file mode 100644 index 753f05d..0000000 --- a/lib/SI7006A20.py +++ /dev/null @@ -1,32 +0,0 @@ -import time -from machine import I2C - -class SI7006A20: - SI7006A20_I2C_ADDR = const(0x40) - TEMP_NOHOLDMASTER = const(0xF3) - HUMD_NOHOLDMASTER = const(0xF5) - - def __init__(self, pysense = None, sda = 'P22', scl = 'P21'): - if pysense is not None: - self.i2c = pysense.i2c - else: - self.i2c = I2C(0, mode=I2C.MASTER, pins=(sda, scl)) - - def _getWord(self, high, low): - return ((high & 0xFF) << 8) + (low & 0xFF) - - def temperature(self): - self.i2c.writeto(SI7006A20_I2C_ADDR, bytearray([0xF3])) - time.sleep(0.5) - data = self.i2c.readfrom(SI7006A20_I2C_ADDR, 2) - data = self._getWord(data[0], data[1]) - temp = ((175.72 * data) / 65536.0) - 46.85 - return temp - - def humidity(self): - self.i2c.writeto(SI7006A20_I2C_ADDR, bytearray([0xF5])) - time.sleep(0.5) - data = self.i2c.readfrom(SI7006A20_I2C_ADDR, 2) - data = self._getWord(data[0], data[1]) - humidity = ((125.0 * data) / 65536.0) - 6.0 - return humidity diff --git a/lib/__init__.py b/lib/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lib/bh1750fvi.py b/lib/bh1750fvi.py new file mode 100644 index 0000000..5236ef3 --- /dev/null +++ b/lib/bh1750fvi.py @@ -0,0 +1,20 @@ +# Simple driver for the BH1750FVI digital light sensor + +class BH1750FVI: + MEASUREMENT_TIME = const(120) + + def __init__(self, i2c, addr=0x23, period=150): + self.i2c = i2c + self.period = period + self.addr = addr + self.time = 0 + self.value = 0 + self.i2c.writeto(addr, bytes([0x10])) # start continuos 1 Lux readings every 120ms + + def read(self): + self.time += self.period + if self.time >= MEASUREMENT_TIME: + self.time = 0 + data = self.i2c.readfrom(self.addr, 2) + self.value = (((data[0] << 8) + data[1]) * 1200) // 1000 + return self.value \ No newline at end of file diff --git a/lib/bme280.py b/lib/bme280.py new file mode 100644 index 0000000..12a9488 --- /dev/null +++ b/lib/bme280.py @@ -0,0 +1,292 @@ +from machine import I2C +import time + +# BME280 default address. +BME280_I2CADDR = 0x76 + +# 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 + return (self.t_fine * 5 + 128) >> 8 + + def read_pressure(self): + """Gets the compensated pressure in Pascals.""" + 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 + return ((p + var1 + var2) >> 8) + (self.dig_P7 << 4) + + 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 + return h >> 12 + + @property + def temperature(self): + "Return the temperature in degrees." + t = self.read_temperature() + ti = t // 100 + td = t - ti * 100 + return "{}.{:02d}".format(ti, td) + #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) + return "{}.{:02d}".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) + return "{}.{:02d}".format(hi, hd) diff --git a/lib/deepsleep.py b/lib/deepsleep.py new file mode 100644 index 0000000..10b0c30 --- /dev/null +++ b/lib/deepsleep.py @@ -0,0 +1,188 @@ +from machine import UART +from machine import Pin +import pycom +import gc + +__version__ = '1.0.5' + + +PIN_MASK = 0b1010 +COMM_PIN = 'P10' + +PIN_WAKE = 0 +TIMER_WAKE = 1 << 4 +POWER_ON_WAKE = 1 << 5 + + +class DeepSleep: + + WPUA_ADDR = const(0x09) + OPTION_REG_ADDR = const(0x0E) + IOCAP_ADDR = const(0x1A) + IOCAN_ADDR = const(0x1B) + + WAKE_STATUS_ADDR = const(0x40) + MIN_BAT_ADDR = const(0x41) + SLEEP_TIME_ADDR = const(0x42) + CTRL_0_ADDR = const(0x45) + + EXP_RTC_PERIOD = const(7000) + + def __init__(self): + self.uart = UART(1, baudrate=10000, pins=(COMM_PIN, ), timeout_chars=5) + self.clk_cal_factor = 1 + self.uart.read() + # enable the weak pull-ups control + self.clearbits(OPTION_REG_ADDR, 1 << 7) + + def _send(self, data): + self.uart.write(bytes(data)) + + def _start(self): + self.uart.sendbreak(12) + self._send([0x55]) + + def _magic(self, address, and_val, or_val, xor_val, expected=None): + self._start() + self._send([address, and_val & 0xFF, or_val & 0xFF, xor_val & 0xFF]) + if expected is None: + return self.uart.read() + else: + if expected > 0: + return self.uart.read(expected) + + def _add_to_pin_mask(self, mask, pin): + if pin == 'P10' or pin == 'G17': + mask |= 0x01 + elif pin == 'P17' or pin == 'G31': + mask |= 0x02 + elif pin == 'P18' or pin == 'G30': + mask |= 0x08 + else: + raise ValueError('Invalid Pin specified: {}'.format(pin)) + return mask + + def _create_pin_mask(self, pins): + mask = 0 + if type(pins) is str: + mask = self._add_to_pin_mask(mask, pins) + else: + for pin in pins: + mask = self._add_to_pin_mask(mask, pin) + return mask & PIN_MASK + + def poke(self, address, value): + self._magic(address, 0, value, 0) + + def peek(self, address): + try: + return self._magic(address, 0xFF, 0, 0)[6] + except: + return self._magic(address, 0xFF, 0, 0)[6] + + def setbits(self, address, mask): + self._magic(address, 0xFF, mask, 0) + + def clearbits(self, address, mask): + self._magic(address, ~mask, 0, 0) + + def togglebits(self, address, mask): + self._magic(address, 0xFF, 0, mask) + + def calibrate(self): + """ The microcontroller will send the value of CTRL_0 after setting the bit + and then will send the following pattern through the data line: + + val | 1 | 0 | 1*| 0 | 1*| 0 | 1 + ms | 1 | 1 | 1 | 1 | 8 | 1 | - + + The idea is to measure the real life duration of periods marked with * + and substract them. That will remove any errors common to both measurements + The result is 7 ms as generated by the PIC LF clock. + It can be used to scale any future sleep value. """ + + # setbits, but limit the number of received bytes to avoid confusion with pattern + self._magic(CTRL_0_ADDR, 0xFF, 1 << 2, 0, 0) + self.uart.deinit() + self._pulses = pycom.pulses_get(COMM_PIN, 150) + self.uart.init(baudrate=10000, pins=(COMM_PIN, ), timeout_chars=5) + idx = 0 + for i in range(len(self._pulses)): + if self._pulses[i][1] > EXP_RTC_PERIOD: + idx = i + break + try: + self.clk_cal_factor = (self._pulses[idx][1] - self._pulses[(idx - 1)][1]) / EXP_RTC_PERIOD + except: + self.clk_cal_factor = 1 + if self.clk_cal_factor > 1.25 or self.clk_cal_factor < 0.75: + self.clk_cal_factor = 1 + + def enable_auto_poweroff(self): + self.setbits(CTRL_0_ADDR, 1 << 1) + + def enable_pullups(self, pins): + mask = self._create_pin_mask(pins) + self.setbits(WPUA_ADDR, mask) + + def disable_pullups(self, pins): + mask = self._create_pin_mask(pins) + self.clearbits(WPUA_ADDR, mask) + + def enable_wake_on_raise(self, pins): + mask = self._create_pin_mask(pins) + self.setbits(IOCAP_ADDR, mask) + + def disable_wake_on_raise(self, pins): + mask = self._create_pin_mask(pins) + self.clearbits(IOCAP_ADDR, mask) + + def enable_wake_on_fall(self, pins): + mask = self._create_pin_mask(pins) + self.setbits(IOCAN_ADDR, mask) + + def disable_wake_on_fall(self, pins): + mask = self._create_pin_mask(pins) + self.clearbits(IOCAN_ADDR, mask) + + def get_wake_status(self): + # bits as they are returned from PIC: + # 0: PIN 0 value after awake + # 1: PIN 1 value after awake + # 2: PIN 2 value after awake + # 3: PIN 3 value after awake + # 4: TIMEOUT + # 5: POWER ON + + wake_r = self.peek(WAKE_STATUS_ADDR) + return {'wake': wake_r & (TIMER_WAKE | POWER_ON_WAKE), + 'P10': wake_r & 0x01, 'P17': (wake_r & 0x02) >> 1, + 'P18': (wake_r & 0x08) >> 3} + + def set_min_voltage_limit(self, value): + # voltage value passed in volts (e.g. 3.6) and round it to the nearest integer + value = int(((256 * 2.048) + (value / 2)) / value) + self.poke(MIN_BAT_ADDR, value) + + def go_to_sleep(self, seconds): + gc.collect() + while True: + try: + self.calibrate() + except Exception: + pass + + # the 1.024 factor is because the PIC LF operates at 31 KHz + # WDT has a frequency divider to generate 1 ms + # and then there is a binary prescaler, e.g., 1, 2, 4 ... 512, 1024 ms + # hence the need for the constant + + # round to the nearest integer + seconds = int((seconds / (1.024 * self.clk_cal_factor)) + 0.5) + self.poke(SLEEP_TIME_ADDR, (seconds >> 16) & 0xFF) + self.poke(SLEEP_TIME_ADDR + 1, (seconds >> 8) & 0xFF) + self.poke(SLEEP_TIME_ADDR + 2, seconds & 0xFF) + self.setbits(CTRL_0_ADDR, 1 << 0) + + def hw_reset(self): + self.setbits(CTRL_0_ADDR, 1 << 4) diff --git a/lib/loranode.py b/lib/loranode.py new file mode 100644 index 0000000..8726637 --- /dev/null +++ b/lib/loranode.py @@ -0,0 +1,94 @@ +import socket +import struct +import time +from network import LoRa +import crypto +from crypto import AES + +# A basic package header +# B: 1 byte for the deviceId +# B: 1 byte for the pkg size +# B: 1 byte for the messageId +# %ds: Formated string for string +_LORA_PKG_FORMAT = "!BB%ds" + +# A basic ack package +# B: 1 byte for the deviceId +# B: 1 byte for the pkg size +# B: 1 byte for the messageId +# B: 1 byte for the Ok (200) or error messages +_LORA_PKG_ACK_FORMAT = "BBB" + + +class LoraNodeRaw(): + + _MAX_ACK_TIME = 5000 + _RETRY_COUNT = 3 + + def __init__(self, conf=None): + # conf: { + # "deviceId": integer[0...255] + # } + self._DEVICE_ID = 0x01 + self.conf = conf + if conf is not None and 'deviceId' in conf: + self._DEVICE_ID = conf['deviceId'] + + self.msg_id = 0 + self.lora = LoRa(mode=LoRa.LORA, tx_iq=True, region=LoRa.EU868) + self.lora_sock = socket.socket(socket.AF_LORA, socket.SOCK_RAW) + self.lora_sock.setblocking(False) + + def increase_msg_id(self): + self.msg_id = (self.msg_id + 1) & 0xFF + + # Method for acknoledge waiting time keep + def check_ack_time(self, from_time): + current_time = time.ticks_ms() + return (current_time - from_time > self._MAX_ACK_TIME) + + # Method to send messages + def send_msg(self, msg): + + # Encrypting message if key present in conf + if self.conf is not None and 'key' in self.conf: + # hardware generated random IV + iv = crypto.getrandbits(128) + cipher = AES( + self.conf['key'], + AES.MODE_CFB, iv + ) + msg = iv + cipher.encrypt(b'%s' % msg) + + retry = self._RETRY_COUNT + sent = False + while (retry > 0): + retry -= 1 + pkg = struct.pack( + _LORA_PKG_FORMAT % len(msg), + self._DEVICE_ID, + len(msg), + msg + ) + self.lora_sock.send(pkg) + + # Wait for the response from the server. + start_time = time.ticks_ms() + + while(not self.check_ack_time(start_time)): + recv_ack = self.lora_sock.recv(256) + # If a message of the size of the acknoledge + # message is received + if (len(recv_ack) > 0): + device_id, pkg_len, ack = struct.unpack(_LORA_PKG_ACK_FORMAT, recv_ack) + if (device_id == self._DEVICE_ID): + if (ack == 200): + sent = True + # If the uart = machine.UART(0, 115200) and os.dupterm(uart) are set in the boot.py this print should appear in the serial port + print("ACK") + retry = 0 + return + + raise Exception("Message not sent after {} attempt".format( + self._RETRY_COUNT + )) diff --git a/lib/onewire.py b/lib/onewire.py index cc76ebf..7e7259c 100644 --- a/lib/onewire.py +++ b/lib/onewire.py @@ -1,9 +1,3 @@ -#!/usr/bin/env python3 - -""" -OneWire library for MicroPython -""" - import time import machine @@ -42,7 +36,7 @@ def read_bit(self): enable_irq = machine.enable_irq pin = self.pin - pin(1) # half of the devices don't match CRC without this line + pin(1) # half of the devices don't match CRC without this line i = machine.disable_irq() pin(0) sleep_us(1) diff --git a/lib/pycoproc.py b/lib/pycoproc.py deleted file mode 100644 index 4533718..0000000 --- a/lib/pycoproc.py +++ /dev/null @@ -1,275 +0,0 @@ -from machine import Pin -from machine import I2C -import time -import pycom - -__version__ = '0.0.1' - -""" PIC MCU wakeup reason types """ -WAKE_REASON_ACCELEROMETER = 1 -WAKE_REASON_PUSH_BUTTON = 2 -WAKE_REASON_TIMER = 4 -WAKE_REASON_INT_PIN = 8 - -class Pycoproc: - """ class for handling interraction with PIC MCU """ - - I2C_SLAVE_ADDR = const(8) - - CMD_PEEK = const(0x0) - CMD_POKE = const(0x01) - CMD_MAGIC = const(0x02) - CMD_HW_VER = const(0x10) - CMD_FW_VER = const(0x11) - CMD_PROD_ID = const(0x12) - CMD_SETUP_SLEEP = const(0x20) - CMD_GO_SLEEP = const(0x21) - CMD_CALIBRATE = const(0x22) - CMD_BAUD_CHANGE = const(0x30) - CMD_DFU = const(0x31) - - REG_CMD = const(0) - REG_ADDRL = const(1) - REG_ADDRH = const(2) - REG_AND = const(3) - REG_OR = const(4) - REG_XOR = const(5) - - ANSELA_ADDR = const(0x18C) - ANSELB_ADDR = const(0x18D) - ANSELC_ADDR = const(0x18E) - - ADCON0_ADDR = const(0x9D) - ADCON1_ADDR = const(0x9E) - - IOCAP_ADDR = const(0x391) - IOCAN_ADDR = const(0x392) - - INTCON_ADDR = const(0x0B) - OPTION_REG_ADDR = const(0x95) - - _ADCON0_CHS_POSN = const(0x02) - _ADCON0_ADON_MASK = const(0x01) - _ADCON1_ADCS_POSN = const(0x04) - _ADCON0_GO_nDONE_MASK = const(0x02) - - ADRESL_ADDR = const(0x09B) - ADRESH_ADDR = const(0x09C) - - TRISC_ADDR = const(0x08E) - - PORTA_ADDR = const(0x00C) - PORTC_ADDR = const(0x00E) - - WPUA_ADDR = const(0x20C) - - WAKE_REASON_ADDR = const(0x064C) - MEMORY_BANK_ADDR = const(0x0620) - - PCON_ADDR = const(0x096) - STATUS_ADDR = const(0x083) - - EXP_RTC_PERIOD = const(7000) - - def __init__(self, i2c=None, sda='P22', scl='P21'): - if i2c is not None: - self.i2c = i2c - else: - self.i2c = I2C(0, mode=I2C.MASTER, pins=(sda, scl)) - - self.sda = sda - self.scl = scl - self.clk_cal_factor = 1 - self.reg = bytearray(6) - self.wake_int = False - self.wake_int_pin = False - self.wake_int_pin_rising_edge = True - - try: - self.read_fw_version() - except Exception: - time.sleep_ms(2) - try: - # init the ADC for the battery measurements - self.poke_memory(ANSELC_ADDR, 1 << 2) - self.poke_memory(ADCON0_ADDR, (0x06 << _ADCON0_CHS_POSN) | _ADCON0_ADON_MASK) - self.poke_memory(ADCON1_ADDR, (0x06 << _ADCON1_ADCS_POSN)) - # enable the pull-up on RA3 - self.poke_memory(WPUA_ADDR, (1 << 3)) - # make RC5 an input - self.set_bits_in_memory(TRISC_ADDR, 1 << 5) - # set RC6 and RC7 as outputs and enable power to the sensors and the GPS - self.mask_bits_in_memory(TRISC_ADDR, ~(1 << 6)) - self.mask_bits_in_memory(TRISC_ADDR, ~(1 << 7)) - - if self.read_fw_version() < 6: - raise ValueError('Firmware out of date') - - except Exception: - raise Exception('Board not detected') - - def _write(self, data, wait=True): - self.i2c.writeto(I2C_SLAVE_ADDR, data) - if wait: - self._wait() - - def _read(self, size): - return self.i2c.readfrom(I2C_SLAVE_ADDR, size + 1)[1:(size + 1)] - - def _wait(self): - count = 0 - time.sleep_us(10) - while self.i2c.readfrom(I2C_SLAVE_ADDR, 1)[0] != 0xFF: - time.sleep_us(100) - count += 1 - if (count > 500): # timeout after 50ms - raise Exception('Board timeout') - - def _send_cmd(self, cmd): - self._write(bytes([cmd])) - - def read_hw_version(self): - self._send_cmd(CMD_HW_VER) - d = self._read(2) - return (d[1] << 8) + d[0] - - def read_fw_version(self): - self._send_cmd(CMD_FW_VER) - d = self._read(2) - return (d[1] << 8) + d[0] - - def read_product_id(self): - self._send_cmd(CMD_PROD_ID) - d = self._read(2) - return (d[1] << 8) + d[0] - - def peek_memory(self, addr): - self._write(bytes([CMD_PEEK, addr & 0xFF, (addr >> 8) & 0xFF])) - return self._read(1)[0] - - def poke_memory(self, addr, value): - self._write(bytes([CMD_POKE, addr & 0xFF, (addr >> 8) & 0xFF, value & 0xFF])) - - def magic_write_read(self, addr, _and=0xFF, _or=0, _xor=0): - self._write(bytes([CMD_MAGIC, addr & 0xFF, (addr >> 8) & 0xFF, _and & 0xFF, _or & 0xFF, _xor & 0xFF])) - return self._read(1)[0] - - def toggle_bits_in_memory(self, addr, bits): - self.magic_write_read(addr, _xor=bits) - - def mask_bits_in_memory(self, addr, mask): - self.magic_write_read(addr, _and=mask) - - def set_bits_in_memory(self, addr, bits): - self.magic_write_read(addr, _or=bits) - - def get_wake_reason(self): - """ returns the wakeup reason, a value out of constants WAKE_REASON_* """ - return self.peek_memory(WAKE_REASON_ADDR) - - def get_sleep_remaining(self): - """ returns the remaining time from sleep, as an interrupt (wakeup source) might have triggered """ - c3 = self.peek_memory(WAKE_REASON_ADDR + 3) - c2 = self.peek_memory(WAKE_REASON_ADDR + 2) - c1 = self.peek_memory(WAKE_REASON_ADDR + 1) - time_device_s = (c3 << 16) + (c2 << 8) + c1 - # this time is from PIC internal oscilator, so it needs to be adjusted with the calibration value - try: - self.calibrate_rtc() - except Exception: - pass - time_s = int((time_device_s / self.clk_cal_factor) + 0.5) # 0.5 used for round - return time_s - - def setup_sleep(self, time_s): - try: - self.calibrate_rtc() - except Exception: - pass - time_s = int((time_s * self.clk_cal_factor) + 0.5) # round to the nearest integer - self._write(bytes([CMD_SETUP_SLEEP, time_s & 0xFF, (time_s >> 8) & 0xFF, (time_s >> 16) & 0xFF])) - - def go_to_sleep(self, gps=True): - # enable or disable back-up power to the GPS receiver - if gps: - self.set_bits_in_memory(PORTC_ADDR, 1 << 7) - else: - self.mask_bits_in_memory(PORTC_ADDR, ~(1 << 7)) - # disable the ADC - self.poke_memory(ADCON0_ADDR, 0) - - if self.wake_int: - # Don't touch RA3, RA5 or RC1 so that interrupt wake-up works - self.poke_memory(ANSELA_ADDR, ~((1 << 3) | (1 << 5))) - self.poke_memory(ANSELC_ADDR, ~((1 << 6) | (1 << 7) | (1 << 1))) - else: - # disable power to the accelerometer, and don't touch RA3 so that button wake-up works - self.poke_memory(ANSELA_ADDR, ~(1 << 3)) - self.poke_memory(ANSELC_ADDR, ~(1 << 7)) - - self.poke_memory(ANSELB_ADDR, 0xFF) - - # check if INT pin (PIC RC1), should be used for wakeup - if self.wake_int_pin: - if self.wake_int_pin_rising_edge: - self.set_bits_in_memory(OPTION_REG_ADDR, 1 << 6) # rising edge of INT pin - else: - self.mask_bits_in_memory(OPTION_REG_ADDR, ~(1 << 6)) # falling edge of INT pin - self.mask_bits_in_memory(ANSELC_ADDR, ~(1 << 1)) # disable analog function for RC1 pin - self.set_bits_in_memory(TRISC_ADDR, 1 << 1) # make RC1 input pin - self.mask_bits_in_memory(INTCON_ADDR, ~(1 << 1)) # clear INTF - self.set_bits_in_memory(INTCON_ADDR, 1 << 4) # enable interrupt; set INTE) - - self._write(bytes([CMD_GO_SLEEP]), wait=False) - # kill the run pin - Pin('P3', mode=Pin.OUT, value=0) - - def calibrate_rtc(self): - # the 1.024 factor is because the PIC LF operates at 31 KHz - # WDT has a frequency divider to generate 1 ms - # and then there is a binary prescaler, e.g., 1, 2, 4 ... 512, 1024 ms - # hence the need for the constant - self._write(bytes([CMD_CALIBRATE]), wait=False) - self.i2c.deinit() - Pin('P21', mode=Pin.IN) - pulses = pycom.pulses_get('P21', 50) - self.i2c.init(mode=I2C.MASTER, pins=(self.sda, self.scl)) - try: - period = pulses[2][1] - pulses[0][1] - except: - pass - if period > 0: - self.clk_cal_factor = (EXP_RTC_PERIOD / period) * (1000 / 1024) - - def button_pressed(self): - button = self.peek_memory(PORTA_ADDR) & (1 << 3) - return not button - - def read_battery_voltage(self): - self.set_bits_in_memory(ADCON0_ADDR, _ADCON0_GO_nDONE_MASK) - time.sleep_us(50) - while self.peek_memory(ADCON0_ADDR) & _ADCON0_GO_nDONE_MASK: - time.sleep_us(100) - adc_val = (self.peek_memory(ADRESH_ADDR) << 2) + (self.peek_memory(ADRESL_ADDR) >> 6) - return (((adc_val * 3.3 * 280) / 1023) / 180) + 0.01 # add 10mV to compensate for the drop in the FET - - def setup_int_wake_up(self, rising, falling): - """ rising is for activity detection, falling for inactivity """ - wake_int = False - if rising: - self.set_bits_in_memory(IOCAP_ADDR, 1 << 5) - wake_int = True - else: - self.mask_bits_in_memory(IOCAP_ADDR, ~(1 << 5)) - - if falling: - self.set_bits_in_memory(IOCAN_ADDR, 1 << 5) - wake_int = True - else: - self.mask_bits_in_memory(IOCAN_ADDR, ~(1 << 5)) - self.wake_int = wake_int - - def setup_int_pin_wake_up(self, rising_edge = True): - """ allows wakeup to be made by the INT pin (PIC -RC1) """ - self.wake_int_pin = True - self.wake_int_pin_rising_edge = rising_edge diff --git a/lib/pysense.py b/lib/pysense.py deleted file mode 100644 index 394f812..0000000 --- a/lib/pysense.py +++ /dev/null @@ -1,8 +0,0 @@ -from pycoproc import Pycoproc - -__version__ = '1.4.0' - -class Pysense(Pycoproc): - - def __init__(self, i2c=None, sda='P22', scl='P21'): - Pycoproc.__init__(self, i2c, sda, scl) diff --git a/lib/sensors.py b/lib/sensors.py new file mode 100644 index 0000000..38ea8fd --- /dev/null +++ b/lib/sensors.py @@ -0,0 +1,169 @@ +import pycom +import time +from machine import I2C +from machine import Pin +import ujson + + +class Sensors(): + + def __init__(self, conf): + self.conf = conf + if conf['board'] == 'shield': + from onewire import OneWire + from onewire import DS18X20 + from bme280 import BME280, BME280_I2CADDR + from bh1750fvi import BH1750FVI + + self.BME280 = BME280 + self.BME280_I2CADDR = BME280_I2CADDR + self.BH1750FVI = BH1750FVI + self.DS18X20 = DS18X20 + + #i2c = I2C(0, I2C.MASTER, baudrate=10000) + self.i2c = I2C( + 0, + pins=( + conf['type']['shield']['scl'], + conf['type']['shield']['sda'] + ) + ) + + # set temperature on Pin 4 + self.ow1 = OneWire( + Pin(conf['type']['shield']['temp1']) + ) + + # set temperature on Pin 3 + self.ow2 = OneWire( + Pin(conf['type']['shield']['temp2']) + ) + + time.sleep(2) + # scan for devices on the bus + ow1_scan = self.ow1.scan() + ow2_scan = self.ow2.scan() + + if conf["debug"]: + print('scan devices ow1:', ow1_scan) + print('scan devices ow2:', ow2_scan) + + elif conf['board'] == 'shield': + from pysense import Pysense + from LIS2HH12 import LIS2HH12 + from SI7006A20 import SI7006A20 + from LTR329ALS01 import LTR329ALS01 + from MPL3115A2 import MPL3115A2, ALTITUDE, PRESSURE + from onewire import DS18X20 + from onewire import OneWire + + # pysense sensors + self.py = Pysense() + + # Returns height in meters. Mode may also be set to PRESSURE, + # returning a value in Pascals + self.mp = MPL3115A2(py, mode=ALTITUDE) + self.mpp = MPL3115A2(py, mode=PRESSURE) + self.si = SI7006A20(py) + self.lt = LTR329ALS01(py) + self.li = LIS2HH12(py) + + + def get_obs(self, m): + + if self.conf['board'] == 'shield': + + self.i2c.init(I2C.MASTER) + + if self.conf["debug"]: + print("i2c: ", self.i2c.scan()) + + time.sleep(0.5) + bme_sensor = self.BME280(address=self.BME280_I2CADDR, i2c=self.i2c) + time.sleep(0.5) + light_sensor = self.BH1750FVI(self.i2c, addr=self.i2c.scan()[0]) + + time.sleep(0.5) + temp1 = self.DS18X20(self.ow1) + time.sleep(0.5) + temp2 = self.DS18X20(self.ow2) + for i in range(m): + print("reading values....") + obs = self.read(bme_sensor, light_sensor, temp1, temp2) + print(obs) + print('{}/{}'.format(i+1, m)) + self.i2c.deinit() + Pin( + self.conf['type']['shield']['scl'], + mode=Pin.IN + ) + Pin( + self.conf['type']['shield']['sda'], + mode=Pin.IN + ) + elif self.conf['pysense']: + for i in range(m): + print("reading values....") + obs = self.read( None, None, + temp1, temp2, + self.mp, self.mpp, + lt + ) + print(obs) + print('{}/{}'.format(i+1, m)) + return obs + + def read( + self, + bme_sensor=None, + light_sensor=None, + temp1=None, temp2=None, + mp=None, mpp=None, + lt=None): + + if self.conf['debug']: + pycom.rgbled(0xffa500) + + obs = {} + + # MPL3115A2 or BME280 temperature + if bme_sensor: + obs['internal:temperature'] = bme_sensor.temperature + else: + obs['internal:temperature'] = mp.temperature() + + # MPL3115A2 or BME280 pressure in Pa + + if bme_sensor: + obs['internal:pressure'] = bme_sensor.pressure + else: + obs['internal:pressure'] = mpp.pressure() + + + # Pysense or BME280 humidity + if bme_sensor: + obs['internal:air:humidity'] = bme_sensor.humidity + else: + obs['internal:air:humidity'] = si.humidity() + + # Sensor light + # Sensor temperature + + if light_sensor: + obs['internal:lux'] = light_sensor.read() + else: + light = lt.light() + obs['internal:lux:blue'] = light[0] + obs['internal:lux:red'] = light[1] + + # Reading from external sensors + obs['external:water:temperature'] = temp1.read_temp_async() + temp1.start_conversion() + + obs['external:wall:temperature'] = temp2.read_temp_async() + temp2.start_conversion() + + # battery voltage + # obs['sensor:battery'] = py.read_battery_voltage() + + return obs diff --git a/lib/sensors_old.py b/lib/sensors_old.py new file mode 100644 index 0000000..562d674 --- /dev/null +++ b/lib/sensors_old.py @@ -0,0 +1,79 @@ +import pycom +import time +from machine import Pin +from machine import I2C +import ujson +from lib.bme280 import * +from bh1750fvi import * +from lib.onewire import DS18X20 +from lib.onewire import OneWire + +#i2c = I2C(0, I2C.MASTER, baudrate=10000) +i2c = I2C(0, pins=('P9','P10')) + +# set temperature on Pin 4 +ow1 = OneWire(Pin('P3')) + +# set temperature on Pin 3 +ow2 = OneWire(Pin('P8')) + +time.sleep(2) +# scan for devices on the bus +ow1_scan = ow1.scan() +ow2_scan = ow2.scan() +print('scan devices ow1:', ow1_scan) +print('scan devices ow2:', ow2_scan) + + +def get_obs(m): + # MAKE DATA WARMING READINGS + + i2c.init(I2C.MASTER) + + print("i2c: ",i2c.scan()) + time.sleep(0.5) + bme_sensor = BME280(address=BME280_I2CADDR, i2c=i2c) + time.sleep(0.5) + light_sensor = BH1750FVI(i2c, addr=i2c.scan()[0]) + time.sleep(0.5) + temp1 = DS18X20(ow1) + time.sleep(0.5) + temp2 = DS18X20(ow2) + for i in range(m): + print("reading values....") + obs = read(bme_sensor, light_sensor, temp1, temp2) + print(obs) + print('{}/{}'.format(i+1, m)) + i2c.deinit() + Pin('P9', mode=Pin.IN) + Pin('P10', mode=Pin.IN) + return obs + +def read(bme_sensor, light_sensor, temp1, temp2): + with open("config.json", 'r') as cf: + conf = ujson.loads(cf.read()) + if conf['debug']: + pycom.rgbled(0xffa500) + + obs = {} + + # MPL3115A2 temperature + obs['internal:temperature'] = bme_sensor.temperature + + # MPL3115A2 pressure in Pa + obs['internal:pressure'] = bme_sensor.pressure + + # Sensor temperature + obs['internal:air:humidity'] = bme_sensor.humidity + + # Sensor light + obs['internal:lux'] = light_sensor.read() + + # Reading from external sensors + obs['external:water:temperature'] = temp1.read_temp_async() + temp1.start_conversion() + + obs['external:wall:temperature'] = temp2.read_temp_async() + temp2.start_conversion() + + return obs diff --git a/main.py b/main.py index 5acbe42..196058a 100644 --- a/main.py +++ b/main.py @@ -2,61 +2,94 @@ import ujson import time import utime -import sensors +from lib.sensors import Sensors from machine import deepsleep conf = {} + with open("config.json", 'r') as cf: conf = ujson.loads(cf.read()) +if conf['lora'] is True: + from lib.loranode import LoraNodeRaw + lora = LoraNodeRaw(conf['config']['lora']) + + if conf['wifi'] is True: import lib.urequests as requests -cnt = 0 - print("Starting main loop..") # Start monitoring while(True): - now = utime.gmtime(utime.time()) - cnt += 1 - obs = sensors.read() - print('{}/{}'.format(cnt, conf['read_cnt'])) - # Send data after 20 measurements - if cnt == conf['read_cnt']: - if conf['wifi'] is True: - print("Sending via WIFI") - data = "{};{}-{:02d}-{:02d}T{:02d}:{:02d}:{:02d}+0000".format( - conf['sensorid'], - now[0], - now[1], - now[2], - now[3], - now[4], - now[5] - ) - values = [] - for ob in conf['observations']: - values.append( - "{}".format(obs[ob[1]]) - ) - data = '{},{}'.format( - data, - ",".join(values) - ) + + sensors = Sensors(conf) + values = [] + obs = sensors.get_obs(conf['read_cnt']) + + if conf['debug']: + print(obs) + + for ob in conf['observations']: + if conf["debug"]: + print(ob[0]) + values.append( + "{}".format(obs[ob[1]]) + ) + + if conf['lora'] is True: + + # Preparing message for lora excluding timestamp + data = '{};{}'.format( + conf['sensorid'], + ",".join(values) + ) + + if conf['debug']: + print("Sending via Lora") print(data) - # avoid in the future measurements - utime.sleep_ms(10000) - r = requests.post( - conf['server'], - data=data - ) + + try: + lora.send_msg(data) + except Exception as e: + print(str(e)) + + # time.sleep(20) + + + if conf['wifi'] is True: + print("Sending via WIFI") + + now = utime.gmtime(utime.time()) + # FORMAT LATEST READ + data = "{};{}-{:02d}-{:02d}T{:02d}:{:02d}:{:02d}+0000".format( + conf['sensorid'], + now[0], + now[1], + now[2], + now[3], + now[4], + now[5] + ) + + data = '{},{}'.format( + data, + ",".join(values) + ) + + # SEND DATA + r = requests.post( + conf['server'], + data=data + ) + + if conf["debug"]: print(r.text) + if r.text.find("true") > -1: + print("data succesfully sent") + else: + print("error sending data") - if cnt < conf['read_cnt']: - if debug: - pycom.rgbled(0x155119) - time.sleep_ms(500) - else: - cnt = 0 - deepsleep(conf['deepsleep_seconds']*1800000) + print("Going to sleep") + deepsleep(conf["deepsleep_seconds"]*1000) + print("Deepsleep ended") diff --git a/pymakr.conf b/pymakr.conf new file mode 100644 index 0000000..26c314c --- /dev/null +++ b/pymakr.conf @@ -0,0 +1,10 @@ +{ + "address": "/dev/ttyUSB0", + "username": "micro", + "password": "python", + "sync_folder": "", + "open_on_start": true, + "safe_boot_on_upload": true, + "sync_file_types": "py,txt,log,json,xml", + "ctrl_c_on_connect": false +} diff --git a/sensors.py b/sensors.py deleted file mode 100644 index f3acafc..0000000 --- a/sensors.py +++ /dev/null @@ -1,94 +0,0 @@ -import pycom -import time -from machine import Pin -# from machine import RTC -# from machine import deepsleep -from pysense import Pysense -from LIS2HH12 import LIS2HH12 -from SI7006A20 import SI7006A20 -from LTR329ALS01 import LTR329ALS01 -from MPL3115A2 import MPL3115A2, ALTITUDE, PRESSURE -from lib.onewire import DS18X20 -from lib.onewire import OneWire - -# pysense sensors -py = Pysense() - -# Returns height in meters. Mode may also be set to PRESSURE, -# returning a value in Pascals -mp = MPL3115A2(py, mode=ALTITUDE) -mpp = MPL3115A2(py, mode=PRESSURE) -si = SI7006A20(py) -lt = LTR329ALS01(py) -li = LIS2HH12(py) - -# set temperature on Pin 4 -ow1 = OneWire(Pin('P4')) - -# scan for devices on the bus -print('scan devices:', ow1.scan()) - -# set temperature on Pin 8 -ow2 = OneWire(Pin('P8')) -print('scan devices:', ow2.scan()) - -time.sleep(1) -temp1 = DS18X20(ow1) - -time.sleep(1) -temp2 = DS18X20(ow2) - - -def read(): - if debug: - pycom.rgbled(0xffa500) - - obs = { - "": None, - "": None - } - # MPL3115A2 temperature - obs['internal:temperature'] = mp.temperature() - - # MPL3115A2 altitude - obs['internal:altitude'] = mp.altitude() - - # MPL3115A2 pressure in Pa - obs['internal:pressure'] = mpp.pressure() - - # MPL3115A2 temperature - obs['internal:air:temperature'] = si.temperature() - - # Sensor temperature - obs['internal:air:humidity'] = si.humidity() - - # Sensor temperature - light = lt.light() - obs['internal:lux:blue'] = light[0] - obs['internal:lux:red'] = light[1] - - # Sensor acceleration - obs['sensor:acceleration'] = li.acceleration() - - # Sensor roll - obs['sensor:roll'] = li.roll() - - # Sensor pitch - obs['sensor:pitch'] = li.pitch() - - # Sensor pitch - obs['sensor:battery'] = py.read_battery_voltage() - - # Reading from external sensors - - obs['external:water:temperature'] = temp1.read_temp_async() - time.sleep(1) - temp1.start_conversion() - time.sleep(1) - - obs['external:wall:temperature'] = temp2.read_temp_async() - time.sleep(1) - temp2.start_conversion() - time.sleep(1) - - return obs diff --git a/wifi.py b/wifi.py index 3855bf1..f18abaa 100644 --- a/wifi.py +++ b/wifi.py @@ -8,24 +8,26 @@ def connect(conf): """ Execute a wifi connection """ - if debug: + if conf['debug']: pycom.rgbled(0x4286f4) wlan = WLAN(mode=WLAN.STA) rtc = machine.RTC() nets = wlan.scan() for net in nets: - print(net.ssid) - if net.ssid == conf['ssid']: + if conf['debug']: + print(net.ssid) + if net.ssid == conf['config']['wifi']['ssid']: print('Network found!') wlan.connect( net.ssid, auth=( net.sec, - conf['password'] + conf['config']['wifi']['password'] ), timeout=5000 ) while not wlan.isconnected(): + print(".", end="") machine.idle() # save power while waiting print('WLAN connection succeeded!') # setup rtc From b12cd4d85d23f0f3c87738b4a0c99ffa764e88ec Mon Sep 17 00:00:00 2001 From: Daniele Date: Tue, 3 Apr 2018 17:18:36 +0200 Subject: [PATCH 2/5] changed deepsleep to 1800 --- config.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config.json b/config.json index 374a418..a1d2bd1 100644 --- a/config.json +++ b/config.json @@ -1,12 +1,12 @@ { "sensorid": "bdd27ad4276e11e8817a0800273cbaca", "server": "http://istsos.org/istsos/wa/istsos/services/albis/operations/fastinsert", - "debug": true, + "debug": false, "wifi": false, "lora": true, "board": "shield", "read_cnt": 2, - "deepsleep_seconds": 10, + "deepsleep_seconds": 1800, "type": { "pysense": { "temp1": "P22", From ba08c31aaafdbc25a5ab1a24aeb26b19e97ed647 Mon Sep 17 00:00:00 2001 From: Daniele Date: Thu, 5 Apr 2018 09:53:23 +0200 Subject: [PATCH 3/5] added battery level and internal temp --- config.json | 13 +++++++++++-- lib/sensors.py | 22 ++++++++++++++++++++-- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/config.json b/config.json index a1d2bd1..a53fabc 100644 --- a/config.json +++ b/config.json @@ -1,5 +1,5 @@ { - "sensorid": "bdd27ad4276e11e8817a0800273cbaca", + "sensorid": "79445b8a382111e8abb30800273cbaca", "server": "http://istsos.org/istsos/wa/istsos/services/albis/operations/fastinsert", "debug": false, "wifi": false, @@ -16,7 +16,8 @@ "temp1": "P22", "temp2": "P21", "scl": "P9", - "sda": "P10" + "sda": "P10", + "batt": "P16" } }, "config": { @@ -30,6 +31,10 @@ } }, "observations": [ + [ + "urn:ogc:def:parameter:x-istsos:1.0:internal:temperature", + "internal:temperature" + ], [ "urn:ogc:def:parameter:x-istsos:1.0:internal:air:humidity", "internal:air:humidity" @@ -49,6 +54,10 @@ [ "urn:ogc:def:parameter:x-istsos:1.0:external:water:temperature", "external:water:temperature" + ], + [ + "urn:ogc:def:parameter:x-istsos:1.0:sensor:battery", + "sensor:battery" ] ] } diff --git a/lib/sensors.py b/lib/sensors.py index 38ea8fd..8ba37cf 100644 --- a/lib/sensors.py +++ b/lib/sensors.py @@ -2,13 +2,16 @@ import time from machine import I2C from machine import Pin +from machine import ADC import ujson + class Sensors(): def __init__(self, conf): self.conf = conf + if conf['board'] == 'shield': from onewire import OneWire from onewire import DS18X20 @@ -48,6 +51,10 @@ def __init__(self, conf): print('scan devices ow1:', ow1_scan) print('scan devices ow2:', ow2_scan) + # battery pin + adc = ADC() + self.batt = adc.channel(attn=1, pin=conf['type']['shield']['batt']) + elif conf['board'] == 'shield': from pysense import Pysense from LIS2HH12 import LIS2HH12 @@ -68,7 +75,6 @@ def __init__(self, conf): self.lt = LTR329ALS01(py) self.li = LIS2HH12(py) - def get_obs(self, m): if self.conf['board'] == 'shield': @@ -158,12 +164,24 @@ def read( # Reading from external sensors obs['external:water:temperature'] = temp1.read_temp_async() + + # while (obs['external:water:temperature'] == 85): + # obs['external:water:temperature'] = temp1.read_temp_async() + + time.sleep(1) temp1.start_conversion() obs['external:wall:temperature'] = temp2.read_temp_async() + # while (obs['external:wall:temperature'] == 85): + # obs['external:wall:temperature'] = temp1.read_temp_async() + + time.sleep(1) temp2.start_conversion() # battery voltage - # obs['sensor:battery'] = py.read_battery_voltage() + # obs['sensor:battery'] = self.py.read_battery_voltage() + + val = self.batt() # read an analog value + obs['sensor:battery'] = val/1000 return obs From cb3b2084a369307ebca9a85946a0794206ef1afc Mon Sep 17 00:00:00 2001 From: Daniele Date: Tue, 17 Apr 2018 14:57:24 +0200 Subject: [PATCH 4/5] added choice for deepsleep shield --- config.json | 1 + main.py | 11 +++++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/config.json b/config.json index a53fabc..da8ccce 100644 --- a/config.json +++ b/config.json @@ -5,6 +5,7 @@ "wifi": false, "lora": true, "board": "shield", + "deepsleep_shield": false, "read_cnt": 2, "deepsleep_seconds": 1800, "type": { diff --git a/main.py b/main.py index 196058a..d575498 100644 --- a/main.py +++ b/main.py @@ -3,7 +3,11 @@ import time import utime from lib.sensors import Sensors -from machine import deepsleep +if conf['deepsleep_shield']: + from lib.deepsleep import DeepSleep + ds = DeepSleep() +else: + from machine import deepsleep conf = {} @@ -91,5 +95,8 @@ print("error sending data") print("Going to sleep") - deepsleep(conf["deepsleep_seconds"]*1000) + if conf['deepsleep_shield']: + ds.go_to_sleep(conf["deepsleep_seconds"]) + else: + deepsleep(conf["deepsleep_seconds"]*1000) print("Deepsleep ended") From 26b56d5d72fb279937d9ddf880c78959db0e6685 Mon Sep 17 00:00:00 2001 From: Daniele Date: Wed, 18 Apr 2018 11:57:05 +0200 Subject: [PATCH 5/5] added disable wifi on boot --- boot.py | 3 +++ config.json | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/boot.py b/boot.py index 54c5ff0..bf4a3d3 100644 --- a/boot.py +++ b/boot.py @@ -5,6 +5,9 @@ print("\n\nBooting Albis Sensor") +if pycom.wifi_on_boot(): # get the wifi on boot flag + pycom.wifi_on_boot(False) # disable WiFi on boot + with open("config.json", 'r') as cf: conf = ujson.loads(cf.read()) pycom.heartbeat(False) diff --git a/config.json b/config.json index da8ccce..8706f1f 100644 --- a/config.json +++ b/config.json @@ -1,5 +1,5 @@ { - "sensorid": "79445b8a382111e8abb30800273cbaca", + "sensorid": "b2c8e788425611e8a4730800273cbaca", "server": "http://istsos.org/istsos/wa/istsos/services/albis/operations/fastinsert", "debug": false, "wifi": false,