Reorganisation
Add pyportal-controller "sub-project"
This commit is contained in:
277
station-esp8266/lib-src/adafruit_max31865.py
Normal file
277
station-esp8266/lib-src/adafruit_max31865.py
Normal file
@ -0,0 +1,277 @@
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2017 Tony DiCola for Adafruit Industries
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
"""
|
||||
`adafruit_max31865`
|
||||
====================================================
|
||||
|
||||
MicroPython module for the MAX31865 platinum RTD temperature sensor. See
|
||||
examples/simpletest.py for an example of the usage.
|
||||
|
||||
* Author(s): Tony DiCola
|
||||
* micropython adaptation : arofarn
|
||||
|
||||
Implementation Notes
|
||||
--------------------
|
||||
|
||||
**Hardware:**
|
||||
|
||||
* Adafruit `Universal Thermocouple Amplifier MAX31856 Breakout
|
||||
<https://www.adafruit.com/product/3263>`_ (Product ID: 3263)
|
||||
|
||||
* Adafruit `PT100 RTD Temperature Sensor Amplifier - MAX31865
|
||||
<https://www.adafruit.com/product/3328>`_ (Product ID: 3328)
|
||||
|
||||
* Adafruit `PT1000 RTD Temperature Sensor Amplifier - MAX31865
|
||||
<https://www.adafruit.com/product/3648>`_ (Product ID: 3648)
|
||||
|
||||
**Software and Dependencies:**
|
||||
|
||||
* Adafruit CircuitPython firmware for the ESP8622 and M0-based boards:
|
||||
https://github.com/adafruit/circuitpython/releases
|
||||
"""
|
||||
import math
|
||||
import time
|
||||
from machine import Pin
|
||||
|
||||
from micropython import const
|
||||
|
||||
|
||||
__version__ = "0.0.0-auto.0"
|
||||
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_MAX31865.git"
|
||||
|
||||
# pylint: disable=bad-whitespace
|
||||
# Register and other constant values:
|
||||
_MAX31865_CONFIG_REG = const(0x00)
|
||||
_MAX31865_CONFIG_BIAS = const(0x80)
|
||||
_MAX31865_CONFIG_MODEAUTO = const(0x40)
|
||||
_MAX31865_CONFIG_MODEOFF = const(0x00)
|
||||
_MAX31865_CONFIG_1SHOT = const(0x20)
|
||||
_MAX31865_CONFIG_3WIRE = const(0x10)
|
||||
_MAX31865_CONFIG_24WIRE = const(0x00)
|
||||
_MAX31865_CONFIG_FAULTSTAT = const(0x02)
|
||||
_MAX31865_CONFIG_FILT50HZ = const(0x01)
|
||||
_MAX31865_CONFIG_FILT60HZ = const(0x00)
|
||||
_MAX31865_RTDMSB_REG = const(0x01)
|
||||
_MAX31865_RTDLSB_REG = const(0x02)
|
||||
_MAX31865_HFAULTMSB_REG = const(0x03)
|
||||
_MAX31865_HFAULTLSB_REG = const(0x04)
|
||||
_MAX31865_LFAULTMSB_REG = const(0x05)
|
||||
_MAX31865_LFAULTLSB_REG = const(0x06)
|
||||
_MAX31865_FAULTSTAT_REG = const(0x07)
|
||||
_MAX31865_FAULT_HIGHTHRESH = const(0x80)
|
||||
_MAX31865_FAULT_LOWTHRESH = const(0x40)
|
||||
_MAX31865_FAULT_REFINLOW = const(0x20)
|
||||
_MAX31865_FAULT_REFINHIGH = const(0x10)
|
||||
_MAX31865_FAULT_RTDINLOW = const(0x08)
|
||||
_MAX31865_FAULT_OVUV = const(0x04)
|
||||
_RTD_A = 3.9083e-3
|
||||
_RTD_B = -5.775e-7
|
||||
# pylint: enable=bad-whitespace
|
||||
|
||||
|
||||
class MAX31865:
|
||||
"""Driver for the MAX31865 thermocouple amplifier."""
|
||||
|
||||
def __init__(self, spi, cs, *, rtd_nominal=100, ref_resistor=430.0,
|
||||
wires=2, filter_frequency=60.0):
|
||||
self.rtd_nominal = rtd_nominal
|
||||
self.ref_resistor = ref_resistor
|
||||
self._spi = spi
|
||||
self._cs = cs
|
||||
self._cs.init(mode=Pin.OUT, pull=Pin.PULL_UP)
|
||||
self._cs.value(1)
|
||||
|
||||
# Set 50Hz or 60Hz filter.
|
||||
if filter_frequency not in (50, 60):
|
||||
raise ValueError("Filter_frequency must be a value of 50 or 60!")
|
||||
config = self._read_u8(_MAX31865_CONFIG_REG)
|
||||
if filter_frequency == 50:
|
||||
config |= _MAX31865_CONFIG_FILT50HZ
|
||||
else:
|
||||
config &= ~_MAX31865_CONFIG_FILT50HZ
|
||||
|
||||
# Set wire config register based on the number of wires specified.
|
||||
if wires not in (2, 3, 4):
|
||||
raise ValueError("Wires must be a value of 2, 3, or 4!")
|
||||
if wires == 3:
|
||||
config |= _MAX31865_CONFIG_3WIRE
|
||||
else:
|
||||
# 2 or 4 wire
|
||||
config &= ~_MAX31865_CONFIG_3WIRE
|
||||
self._write_u8(_MAX31865_CONFIG_REG, config)
|
||||
# Default to no bias and no auto conversion.
|
||||
self.bias = False
|
||||
self.auto_convert = False
|
||||
|
||||
# pylint: disable=no-member
|
||||
def _read_u8(self, address):
|
||||
# Read an 8-bit unsigned value from the specified 8-bit address.
|
||||
buf = bytearray(1)
|
||||
self._cs.value(0)
|
||||
self._spi.write(bytes([address & 0x7F]))
|
||||
self._spi.readinto(buf, 1)
|
||||
self._cs.value(1)
|
||||
return buf[0]
|
||||
|
||||
def _read_u16(self, address):
|
||||
# Read a 16-bit BE unsigned value from the specified 8-bit address.
|
||||
buf = bytearray(2)
|
||||
self._cs.value(0)
|
||||
self._spi.write(bytes([address & 0x7F]))
|
||||
self._spi.readinto(buf, 1)
|
||||
self._cs.value(1)
|
||||
return (buf[0] << 8) | buf[1]
|
||||
|
||||
def _write_u8(self, address, val):
|
||||
# Write an 8-bit unsigned value to the specified 8-bit address.
|
||||
buf = bytearray(2)
|
||||
buf[0] = (address | 0x80) & 0xFF
|
||||
buf[1] = val & 0xFF
|
||||
self._cs.value(0)
|
||||
self._spi.write(buf)
|
||||
self._cs.value(1)
|
||||
|
||||
# pylint: enable=no-member
|
||||
|
||||
@property
|
||||
def bias(self):
|
||||
"""State of the sensor's bias (True/False)."""
|
||||
return bool(self._read_u8(_MAX31865_CONFIG_REG) & _MAX31865_CONFIG_BIAS)
|
||||
|
||||
@bias.setter
|
||||
def bias(self, val):
|
||||
config = self._read_u8(_MAX31865_CONFIG_REG)
|
||||
if val:
|
||||
config |= _MAX31865_CONFIG_BIAS # Enable bias.
|
||||
else:
|
||||
config &= ~_MAX31865_CONFIG_BIAS # Disable bias.
|
||||
self._write_u8(_MAX31865_CONFIG_REG, config)
|
||||
|
||||
@property
|
||||
def auto_convert(self):
|
||||
"""State of the sensor's automatic conversion
|
||||
mode (True/False).
|
||||
"""
|
||||
return bool(self._read_u8(_MAX31865_CONFIG_REG) & _MAX31865_CONFIG_MODEAUTO)
|
||||
|
||||
@auto_convert.setter
|
||||
def auto_convert(self, val):
|
||||
config = self._read_u8(_MAX31865_CONFIG_REG)
|
||||
if val:
|
||||
config |= _MAX31865_CONFIG_MODEAUTO # Enable auto convert.
|
||||
else:
|
||||
config &= ~_MAX31865_CONFIG_MODEAUTO # Disable auto convert.
|
||||
self._write_u8(_MAX31865_CONFIG_REG, config)
|
||||
|
||||
@property
|
||||
def fault(self):
|
||||
"""The fault state of the sensor. Use ``clear_faults()`` to clear the
|
||||
fault state. Returns a 6-tuple of boolean values which indicate if any
|
||||
faults are present:
|
||||
|
||||
- HIGHTHRESH
|
||||
- LOWTHRESH
|
||||
- REFINLOW
|
||||
- REFINHIGH
|
||||
- RTDINLOW
|
||||
- OVUV
|
||||
"""
|
||||
faults = self._read_u8(_MAX31865_FAULTSTAT_REG)
|
||||
# pylint: disable=bad-whitespace
|
||||
highthresh = bool(faults & _MAX31865_FAULT_HIGHTHRESH)
|
||||
lowthresh = bool(faults & _MAX31865_FAULT_LOWTHRESH)
|
||||
refinlow = bool(faults & _MAX31865_FAULT_REFINLOW)
|
||||
refinhigh = bool(faults & _MAX31865_FAULT_REFINHIGH)
|
||||
rtdinlow = bool(faults & _MAX31865_FAULT_RTDINLOW)
|
||||
ovuv = bool(faults & _MAX31865_FAULT_OVUV)
|
||||
# pylint: enable=bad-whitespace
|
||||
return (highthresh, lowthresh, refinlow, refinhigh, rtdinlow, ovuv)
|
||||
|
||||
def clear_faults(self):
|
||||
"""Clear any fault state previously detected by the sensor."""
|
||||
config = self._read_u8(_MAX31865_CONFIG_REG)
|
||||
config &= ~0x2C
|
||||
config |= _MAX31865_CONFIG_FAULTSTAT
|
||||
self._write_u8(_MAX31865_CONFIG_REG, config)
|
||||
|
||||
def read_rtd(self):
|
||||
"""Perform a raw reading of the thermocouple and return its 15-bit
|
||||
value. You'll need to manually convert this to temperature using the
|
||||
nominal value of the resistance-to-digital conversion and some math. If you just want
|
||||
temperature use the temperature property instead.
|
||||
"""
|
||||
self.clear_faults()
|
||||
self.bias = True
|
||||
time.sleep(0.01)
|
||||
config = self._read_u8(_MAX31865_CONFIG_REG)
|
||||
config |= _MAX31865_CONFIG_1SHOT
|
||||
self._write_u8(_MAX31865_CONFIG_REG, config)
|
||||
time.sleep(0.065)
|
||||
rtd = self._read_u16(_MAX31865_RTDMSB_REG)
|
||||
# Remove fault bit.
|
||||
rtd >>= 1
|
||||
return rtd
|
||||
|
||||
@property
|
||||
def resistance(self):
|
||||
"""Read the resistance of the RTD and return its value in Ohms."""
|
||||
resistance = self.read_rtd()
|
||||
resistance /= 32768
|
||||
resistance *= self.ref_resistor
|
||||
return resistance
|
||||
|
||||
@property
|
||||
def temperature(self):
|
||||
"""Read the temperature of the sensor and return its value in degrees
|
||||
Celsius.
|
||||
"""
|
||||
# This math originates from:
|
||||
# http://www.analog.com/media/en/technical-documentation/application-notes/AN709_0.pdf
|
||||
# To match the naming from the app note we tell lint to ignore the Z1-4
|
||||
# naming.
|
||||
# pylint: disable=invalid-name
|
||||
raw_reading = self.resistance
|
||||
Z1 = -_RTD_A
|
||||
Z2 = _RTD_A * _RTD_A - (4 * _RTD_B)
|
||||
Z3 = (4 * _RTD_B) / self.rtd_nominal
|
||||
Z4 = 2 * _RTD_B
|
||||
temp = Z2 + (Z3 * raw_reading)
|
||||
temp = (math.sqrt(temp) + Z1) / Z4
|
||||
if temp >= 0:
|
||||
return temp
|
||||
|
||||
# For the following math to work, nominal RTD resistance must be normalized to 100 ohms
|
||||
raw_reading /= self.rtd_nominal
|
||||
raw_reading *= 100
|
||||
|
||||
rpoly = raw_reading
|
||||
temp = -242.02
|
||||
temp += 2.2228 * rpoly
|
||||
rpoly *= raw_reading # square
|
||||
temp += 2.5859e-3 * rpoly
|
||||
rpoly *= raw_reading # ^3
|
||||
temp -= 4.8260e-6 * rpoly
|
||||
rpoly *= raw_reading # ^4
|
||||
temp -= 2.8183e-8 * rpoly
|
||||
rpoly *= raw_reading # ^5
|
||||
temp += 1.5243e-10 * rpoly
|
||||
return temp
|
559
station-esp8266/lib-src/bme280_i2c.py
Normal file
559
station-esp8266/lib-src/bme280_i2c.py
Normal file
@ -0,0 +1,559 @@
|
||||
# Author(s): Jonathan Hanson 2018
|
||||
#
|
||||
# This is more or less a straight read of the Bosch data sheet at:
|
||||
# https://www.bosch-sensortec.com/bst/products/all_products/bme280
|
||||
# and specifically:
|
||||
# https://ae-bst.resource.bosch.com/media/_tech/media/datasheets/BST-BME280_DS001-12.pdf
|
||||
#
|
||||
# Modeled on the reference library Bosch Sensortec C library at:
|
||||
# https://github.com/BoschSensortec/BME280_driver
|
||||
#
|
||||
# The development of this module was heavily guided by the prior work done
|
||||
# by Peter Dahlberg et al at:
|
||||
# https://github.com/catdog2/mpy_bme280_esp8266
|
||||
|
||||
from micropython import const
|
||||
from ustruct import unpack, unpack_from
|
||||
from utime import sleep_ms
|
||||
|
||||
|
||||
# BME280 default address
|
||||
BME280_I2C_ADDR_PRIM = const(0x76)
|
||||
BME280_I2C_ADDR_SEC = const(0x77)
|
||||
|
||||
# Sensor Power Mode Options
|
||||
BME280_SLEEP_MODE = const(0x00)
|
||||
BME280_FORCED_MODE = const(0x01)
|
||||
BME280_NORMAL_MODE = const(0x03)
|
||||
|
||||
# Oversampling Options
|
||||
BME280_NO_OVERSAMPLING = const(0x00)
|
||||
BME280_OVERSAMPLING_1X = const(0x01)
|
||||
BME280_OVERSAMPLING_2X = const(0x02)
|
||||
BME280_OVERSAMPLING_4X = const(0x03)
|
||||
BME280_OVERSAMPLING_8X = const(0x04)
|
||||
BME280_OVERSAMPLING_16X = const(0x05)
|
||||
|
||||
# Standby Duration Options
|
||||
BME280_STANDBY_TIME_500_US = const(0x00) # Note this is microseconds, so 0.5 ms
|
||||
BME280_STANDBY_TIME_62_5_MS = const(0x01)
|
||||
BME280_STANDBY_TIME_125_MS = const(0x02)
|
||||
BME280_STANDBY_TIME_250_MS = const(0x03)
|
||||
BME280_STANDBY_TIME_500_MS = const(0x04)
|
||||
BME280_STANDBY_TIME_1000_MS = const(0x05)
|
||||
BME280_STANDBY_TIME_10_MS = const(0x06)
|
||||
BME280_STANDBY_TIME_20_MS = const(0x07)
|
||||
|
||||
# Filter Coefficient Options
|
||||
BME280_FILTER_COEFF_OFF = const(0x00)
|
||||
BME280_FILTER_COEFF_2 = const(0x01)
|
||||
BME280_FILTER_COEFF_4 = const(0x02)
|
||||
BME280_FILTER_COEFF_8 = const(0x03)
|
||||
BME280_FILTER_COEFF_16 = const(0x04)
|
||||
|
||||
# BME280 Chip ID
|
||||
_BME280_CHIP_ID = const(0x60)
|
||||
|
||||
# Register Addresses
|
||||
_BME280_CHIP_ID_ADDR = const(0xD0)
|
||||
_BME280_RESET_ADDR = const(0xE0)
|
||||
_BME280_TEMP_PRESS_CALIB_DATA_ADDR = const(0x88)
|
||||
_BME280_HUMIDITY_CALIB_DATA_ADDR = const(0xE1)
|
||||
_BME280_PWR_CTRL_ADDR = const(0xF4)
|
||||
_BME280_CTRL_HUM_ADDR = const(0xF2)
|
||||
_BME280_CTRL_MEAS_ADDR = const(0xF4)
|
||||
_BME280_CONFIG_ADDR = const(0xF5)
|
||||
_BME280_DATA_ADDR = const(0xF7)
|
||||
|
||||
# Register range sizes
|
||||
_BME280_TEMP_PRESS_CALIB_DATA_LEN = const(26)
|
||||
_BME280_HUMIDITY_CALIB_DATA_LEN = const(7)
|
||||
_BME280_P_T_H_DATA_LEN = const(8)
|
||||
|
||||
|
||||
class BME280_I2C:
|
||||
def __init__(self, address: int = BME280_I2C_ADDR_PRIM, i2c=None):
|
||||
"""
|
||||
Ensure I2C communication with the sensor is working, reset the sensor,
|
||||
and load its calibration data into memory.
|
||||
"""
|
||||
self.address = address
|
||||
|
||||
if i2c is None:
|
||||
raise ValueError('A configured I2C object is required.')
|
||||
self.i2c = i2c
|
||||
|
||||
self._read_chip_id()
|
||||
self._soft_reset()
|
||||
self._load_calibration_data()
|
||||
|
||||
def _read_chip_id(self):
|
||||
"""
|
||||
Read the chip ID from the sensor and verify it's correct.
|
||||
If the value isn't correct, wait 1ms and try again.
|
||||
If 5 tries don't work, raise an exception.
|
||||
"""
|
||||
for x in range(5):
|
||||
mem = self.i2c.readfrom_mem(self.address, _BME280_CHIP_ID_ADDR, 1)
|
||||
if mem[0] == _BME280_CHIP_ID:
|
||||
return
|
||||
sleep_ms(1)
|
||||
raise Exception("Couldn't read BME280 chip ID after 5 attempts.")
|
||||
|
||||
def _soft_reset(self):
|
||||
"""
|
||||
Write the reset command to the sensor's reset address.
|
||||
Wait 2ms, per the reference library's example.
|
||||
"""
|
||||
self.i2c.writeto_mem(self.address, _BME280_RESET_ADDR, bytearray([0xB6]))
|
||||
sleep_ms(2)
|
||||
|
||||
def _load_calibration_data(self):
|
||||
"""
|
||||
Load the read-only calibration values out of the sensor's memory, to be
|
||||
used later in calibrating the raw reads. These get stored in various
|
||||
self.cal_dig_* object properties.
|
||||
|
||||
See https://github.com/BoschSensortec/BME280_driver/blob/bme280_v3.3.4/bme280.c#L1192
|
||||
See https://github.com/BoschSensortec/BME280_driver/blob/bme280_v3.3.4/bme280.c#L1216
|
||||
See https://github.com/catdog2/mpy_bme280_esp8266/blob/master/bme280.py#L73
|
||||
"""
|
||||
# Load the temperature and pressure calibration data
|
||||
# (note that the first value of the humidity data is stuffed in here)
|
||||
tp_cal_mem = self.i2c.readfrom_mem(self.address,
|
||||
_BME280_TEMP_PRESS_CALIB_DATA_ADDR,
|
||||
_BME280_TEMP_PRESS_CALIB_DATA_LEN)
|
||||
|
||||
(self.cal_dig_T1, self.cal_dig_T2, self.cal_dig_T3,
|
||||
self.cal_dig_P1, self.cal_dig_P2, self.cal_dig_P3,
|
||||
self.cal_dig_P4, self.cal_dig_P5, self.cal_dig_P6,
|
||||
self.cal_dig_P7, self.cal_dig_P8, self.cal_dig_P9,
|
||||
_,
|
||||
self.cal_dig_H1) = unpack("<HhhHhhhhhhhhBB", tp_cal_mem)
|
||||
|
||||
# Load the rest of the humidity calibration data
|
||||
hum_cal_mem = self.i2c.readfrom_mem(self.address,
|
||||
_BME280_HUMIDITY_CALIB_DATA_ADDR,
|
||||
_BME280_HUMIDITY_CALIB_DATA_LEN)
|
||||
|
||||
self.cal_dig_H2, self.cal_dig_H3 = unpack("<hB", hum_cal_mem)
|
||||
|
||||
e4_sign = unpack_from("<b", hum_cal_mem, 3)[0]
|
||||
self.cal_dig_H4 = (e4_sign << 4) | (hum_cal_mem[4] & 0b00001111)
|
||||
|
||||
e6_sign = unpack_from("<b", hum_cal_mem, 5)[0]
|
||||
self.cal_dig_H5 = (e6_sign << 4) | (hum_cal_mem[4] >> 4)
|
||||
|
||||
self.cal_dig_H6 = unpack_from("<b", hum_cal_mem, 6)[0]
|
||||
|
||||
# Initialize the cal_t_fine carry-over value used during compensation
|
||||
self.cal_t_fine = 0
|
||||
|
||||
def get_measurement_settings(self):
|
||||
"""
|
||||
Return a parsed set of the sensor's measurement settings as a dict
|
||||
These values include oversampling settings for each measurement,
|
||||
the IIR filter coefficient, and the standby duration for normal
|
||||
power mode.
|
||||
See the data sheet, section 3 and 5
|
||||
"""
|
||||
mem = self.i2c.readfrom_mem(self.address, _BME280_CTRL_HUM_ADDR, 4)
|
||||
ctrl_hum, _, ctrl_meas, config = unpack("<BBBB", mem)
|
||||
|
||||
return {
|
||||
"osr_h": (ctrl_hum & 0b00000111),
|
||||
"osr_p": (ctrl_meas >> 2) & 0b00000111,
|
||||
"osr_t": (ctrl_meas >> 5) & 0b00000111,
|
||||
"filter": (config >> 2) & 0b00000111,
|
||||
"standby_time": (config >> 5) & 0b00000111,
|
||||
}
|
||||
|
||||
def set_measurement_settings(self, settings: dict):
|
||||
"""
|
||||
Set the sensor's settings for each measurement's oversampling,
|
||||
the pressure IIR filter coefficient, and standby duration
|
||||
during normal power mode.
|
||||
|
||||
The settings dict can have keys osr_h, osr_p, osr_t, filter, and
|
||||
standby_time. All values are optional, and omitting any will retain
|
||||
the pre-existing value.
|
||||
|
||||
See the data sheet, section 3 and 5
|
||||
"""
|
||||
self._validate_settings(settings)
|
||||
self._ensure_sensor_is_asleep()
|
||||
self._write_measurement_settings(settings)
|
||||
|
||||
def _validate_settings(self, settings: dict):
|
||||
oversampling_options = [
|
||||
BME280_NO_OVERSAMPLING, BME280_OVERSAMPLING_1X,
|
||||
BME280_OVERSAMPLING_2X, BME280_OVERSAMPLING_4X,
|
||||
BME280_OVERSAMPLING_8X, BME280_OVERSAMPLING_16X]
|
||||
|
||||
filter_options = [
|
||||
BME280_FILTER_COEFF_OFF, BME280_FILTER_COEFF_2,
|
||||
BME280_FILTER_COEFF_4, BME280_FILTER_COEFF_8,
|
||||
BME280_FILTER_COEFF_16]
|
||||
|
||||
standby_time_options = [
|
||||
BME280_STANDBY_TIME_500_US,
|
||||
BME280_STANDBY_TIME_62_5_MS, BME280_STANDBY_TIME_125_MS,
|
||||
BME280_STANDBY_TIME_250_MS, BME280_STANDBY_TIME_500_MS,
|
||||
BME280_STANDBY_TIME_1000_MS, BME280_STANDBY_TIME_10_MS,
|
||||
BME280_STANDBY_TIME_20_MS]
|
||||
|
||||
if 'osr_h' in settings:
|
||||
if settings['osr_h'] not in oversampling_options:
|
||||
raise ValueError("osr_h must be one of the oversampling defines")
|
||||
if 'osr_p' in settings:
|
||||
if settings['osr_h'] not in oversampling_options:
|
||||
raise ValueError("osr_p must be one of the oversampling defines")
|
||||
if 'osr_t' in settings:
|
||||
if settings['osr_h'] not in oversampling_options:
|
||||
raise ValueError("osr_t must be one of the oversampling defines")
|
||||
if 'filter' in settings:
|
||||
if settings['filter'] not in filter_options:
|
||||
raise ValueError("filter filter coefficient defines")
|
||||
if 'standby_time' in settings:
|
||||
if settings['standby_time'] not in standby_time_options:
|
||||
raise ValueError("standby_time must be one of the standby time duration defines")
|
||||
|
||||
def _write_measurement_settings(self, settings: dict):
|
||||
# Read in the existing configuration, to modify
|
||||
mem = self.i2c.readfrom_mem(self.address, _BME280_CTRL_HUM_ADDR, 4)
|
||||
ctrl_hum, _, ctrl_meas, config = unpack("<BBBB", mem)
|
||||
|
||||
# Make any changes necessary to the ctrl_hum register
|
||||
if "osr_h" in settings:
|
||||
newval = (ctrl_hum & 0b11111000) | (settings['osr_h'] & 0b00000111)
|
||||
self.i2c.writeto_mem(self.address, _BME280_CTRL_HUM_ADDR, bytearray([newval]))
|
||||
|
||||
# according to the data sheet, ctrl_hum needs a write to
|
||||
# ctrl_meas in order to take effect
|
||||
self.i2c.writeto_mem(self.address, _BME280_CTRL_MEAS_ADDR, bytearray([ctrl_meas]))
|
||||
|
||||
# Make any changes necessary to the ctrl_meas register
|
||||
if "osr_p" in settings or "osr_t" in settings:
|
||||
newval = ctrl_meas
|
||||
if "osr_p" in settings:
|
||||
newval = (newval & 0b11100011) | ((settings['osr_p'] << 2) & 0b00011100)
|
||||
if "osr_t" in settings:
|
||||
newval = (newval & 0b00011111) | ((settings['osr_t'] << 5) & 0b11100000)
|
||||
self.i2c.writeto_mem(self.address, _BME280_CTRL_MEAS_ADDR, bytearray([newval]))
|
||||
|
||||
# Make any changes necessary to the config register
|
||||
if "filter" in settings or "standby_time" in settings:
|
||||
newval = config
|
||||
if "filter" in settings:
|
||||
newval = (newval & 0b11100011) | ((settings['filter'] << 2) & 0b00011100)
|
||||
if "standby_time" in settings:
|
||||
newval = (newval & 0b00011111) | ((settings['standby_time'] << 5) & 0b11100000)
|
||||
self.i2c.writeto_mem(self.address, _BME280_CONFIG_ADDR, bytearray([newval]))
|
||||
|
||||
def get_power_mode(self):
|
||||
"""
|
||||
Result will be one of BME280_SLEEP_MODE, BME280_FORCED_MODE, or
|
||||
BME280_NORMAL_MODE.
|
||||
See the data sheet, section 3.3
|
||||
"""
|
||||
mem = self.i2c.readfrom_mem(self.address, _BME280_PWR_CTRL_ADDR, 1)
|
||||
return (mem[0] & 0b00000011)
|
||||
|
||||
def set_power_mode(self, new_power_mode: int):
|
||||
"""
|
||||
Configure the sensor's power mode (BME280_SLEEP_MODE,
|
||||
BME280_FORCED_MODE, or BME280_NORMAL_MODE)
|
||||
|
||||
Note that setting to forced mode will immediately set the sensor back
|
||||
to sleep mode after taking a measurement.
|
||||
|
||||
See the data sheet, section 3.3
|
||||
"""
|
||||
if new_power_mode not in [BME280_SLEEP_MODE, BME280_FORCED_MODE, BME280_NORMAL_MODE]:
|
||||
raise ValueError("New power mode must be sleep, forced, or normal constant")
|
||||
|
||||
self._ensure_sensor_is_asleep()
|
||||
|
||||
# Read the current register, mask out and set the new power mode,
|
||||
# and write the register back to the device.
|
||||
mem = self.i2c.readfrom_mem(self.address, _BME280_PWR_CTRL_ADDR, 1)
|
||||
newval = (mem[0] & 0b11111100) | (new_power_mode & 0b00000011)
|
||||
self.i2c.writeto_mem(self.address, _BME280_PWR_CTRL_ADDR, bytearray([newval]))
|
||||
|
||||
def _ensure_sensor_is_asleep(self):
|
||||
"""
|
||||
If the sensor mode isn't already "sleep", then put it to sleep.
|
||||
|
||||
This is done by reading out the configuration values we want to keep,
|
||||
and then doing a soft reset and writing those values back.
|
||||
"""
|
||||
if self.get_power_mode() != BME280_SLEEP_MODE:
|
||||
settings = self.get_measurement_settings()
|
||||
self._soft_reset()
|
||||
self._write_measurement_settings(settings)
|
||||
|
||||
def get_measurement(self):
|
||||
"""
|
||||
Return a set of measurements in decimal value, compensated with the
|
||||
sensor's stored calibration data.
|
||||
"""
|
||||
uncompensated_data = self._read_uncompensated_data()
|
||||
|
||||
# Be sure to call self._compensate_temperature() first, as it sets a
|
||||
# global "fine" calibration value for the other two compensation
|
||||
# functions
|
||||
return {
|
||||
"temperature": self._compensate_temperature(uncompensated_data['temperature']),
|
||||
"pressure": self._compensate_pressure(uncompensated_data['pressure']),
|
||||
"humidity": self._compensate_humidity(uncompensated_data['humidity']),
|
||||
}
|
||||
|
||||
def _read_uncompensated_data(self):
|
||||
# Read the uncompensated temperature, pressure, and humidity data
|
||||
mem = self.i2c.readfrom_mem(self.address, _BME280_DATA_ADDR, _BME280_P_T_H_DATA_LEN)
|
||||
(press_msb, press_lsb, press_xlsb,
|
||||
temp_msb, temp_lsb, temp_xlsb,
|
||||
hum_msb, hum_lsb) = unpack("<BBBBBBBB", mem)
|
||||
|
||||
# Assemble the values from the memory fragments and return a dict.
|
||||
#
|
||||
# Note that we're calling temperature first, since it sets the
|
||||
# cal_t_fine value used in humidity and pressure.
|
||||
return {
|
||||
"temperature": (temp_msb << 12) | (temp_lsb << 4) | (temp_xlsb >> 4),
|
||||
"pressure": (press_msb << 12) | (press_lsb << 4) | (press_xlsb >> 4),
|
||||
"humidity": (hum_msb << 8) | (hum_lsb),
|
||||
}
|
||||
|
||||
##
|
||||
# Float Implementations
|
||||
##
|
||||
|
||||
# def _compensate_temperature(self, adc_T: int) -> float:
|
||||
# """
|
||||
# Output value of “25.0” equals 25.0 DegC.
|
||||
#
|
||||
# See the floating-point implementation in the reference library:
|
||||
# https://github.com/BoschSensortec/BME280_driver/blob/bme280_v3.3.4/bme280.c#L884
|
||||
# """
|
||||
# temperature_min = -40
|
||||
# temperature_max = 85
|
||||
#
|
||||
# var1 = (adc_T / 16384.0) - (self.cal_dig_T1 / 1024.0)
|
||||
# var1 = var1 * self.cal_dig_T2
|
||||
#
|
||||
# var2 = (adc_T / 131072.0) - (self.cal_dig_T1 / 8192.0)
|
||||
# var2 = var2 * var2 * self.cal_dig_T3
|
||||
#
|
||||
# self.cal_t_fine = int(var1 + var2)
|
||||
#
|
||||
# temperature = (var1 + var2) / 5120.0
|
||||
#
|
||||
# if temperature < temperature_min:
|
||||
# temperature = temperature_min
|
||||
# elif temperature > temperature_max:
|
||||
# temperature = temperature_max
|
||||
#
|
||||
# return temperature
|
||||
|
||||
# def _compensate_pressure(self, adc_P: int) -> float:
|
||||
# """
|
||||
# Output value of “96386.0” equals 96386 Pa = 963.86 hPa
|
||||
#
|
||||
# See the floating-point implementation in the reference library:
|
||||
# https://github.com/BoschSensortec/BME280_driver/blob/bme280_v3.3.4/bme280.c#L912
|
||||
# """
|
||||
# pressure_min = 30000.0
|
||||
# pressure_max = 110000.0
|
||||
#
|
||||
# var1 = (self.cal_t_fine / 2.0) - 64000.0
|
||||
#
|
||||
# var2 = var1 * var1 * self.cal_dig_P6 / 32768.0
|
||||
# var2 = var2 + (var1 * self.cal_dig_P5 * 2.0)
|
||||
# var2 = (var2 / 4.0) + (self.cal_dig_P4 * 65536.0)
|
||||
#
|
||||
# var3 = self.cal_dig_P3 * var1 * var1 / 524288.0
|
||||
#
|
||||
# var1 = (var3 + self.cal_dig_P2 * var1) / 524288.0
|
||||
# var1 = (1.0 + var1 / 32768.0) * self.cal_dig_P1
|
||||
#
|
||||
# # avoid exception caused by division by zero
|
||||
# if var1:
|
||||
# pressure = 1048576.0 - adc_P
|
||||
# pressure = (pressure - (var2 / 4096.0)) * 6250.0 / var1
|
||||
# var1 = self.cal_dig_P9 * pressure * pressure / 2147483648.0
|
||||
# var2 = pressure * self.cal_dig_P8 / 32768.0
|
||||
# pressure = pressure + (var1 + var2 + self.cal_dig_P7) / 16.0
|
||||
#
|
||||
# if pressure < pressure_min:
|
||||
# pressure = pressure_min
|
||||
# elif pressure > pressure_max:
|
||||
# pressure = pressure_max
|
||||
#
|
||||
# else:
|
||||
# # Invalid case
|
||||
# pressure = pressure_min
|
||||
#
|
||||
# return pressure
|
||||
|
||||
# def _compensate_humidity(self, adc_H: int) -> float:
|
||||
# """
|
||||
# Output value between 0.0 and 100.0, where 100.0 is 100%RH
|
||||
#
|
||||
# See the floating-point implementation in the reference library:
|
||||
# https://github.com/BoschSensortec/BME280_driver/blob/bme280_v3.3.4/bme280.c#L952
|
||||
# """
|
||||
# humidity_min = 0.0
|
||||
# humidity_max = 100.0
|
||||
#
|
||||
# var1 = self.cal_t_fine - 76800.0
|
||||
#
|
||||
# var2 = self.cal_dig_H4 * 64.0 + (self.cal_dig_H5 / 16384.0) * var1
|
||||
#
|
||||
# var3 = adc_H - var2
|
||||
#
|
||||
# var4 = self.cal_dig_H2 / 65536.0
|
||||
#
|
||||
# var5 = 1.0 + (self.cal_dig_H3 / 67108864.0) * var1
|
||||
#
|
||||
# var6 = 1.0 + (self.cal_dig_H6 / 67108864.0) * var1 * var5
|
||||
# var6 = var3 * var4 * (var5 * var6)
|
||||
#
|
||||
# humidity = var6 * (1.0 - self.cal_dig_H1 * var6 / 524288.0)
|
||||
#
|
||||
# if humidity > humidity_max:
|
||||
# humidity = humidity_max
|
||||
# elif humidity < humidity_min:
|
||||
# humidity = humidity_min
|
||||
#
|
||||
# return humidity
|
||||
|
||||
##
|
||||
# 32-Bit Integer Implementations
|
||||
##
|
||||
|
||||
def _compensate_temperature(self, adc_T: int) -> float:
|
||||
"""
|
||||
Output value of “25.0” equals 25.0 DegC.
|
||||
|
||||
See the integer implementation in the data sheet, section 4.2.3
|
||||
And the reference library:
|
||||
https://github.com/BoschSensortec/BME280_driver/blob/bme280_v3.3.4/bme280.c#L987
|
||||
"""
|
||||
temperature_min = -4000
|
||||
temperature_max = 8500
|
||||
|
||||
var1 = (((adc_T // 8) - (self.cal_dig_T1 * 2)) * self.cal_dig_T2) // 2048
|
||||
|
||||
var2 = (((((adc_T // 16) - self.cal_dig_T1) * ((adc_T // 16) - self.cal_dig_T1)) // 4096) * self.cal_dig_T3) // 16384
|
||||
|
||||
self.cal_t_fine = var1 + var2
|
||||
|
||||
temperature = (self.cal_t_fine * 5 + 128) // 256
|
||||
|
||||
if temperature < temperature_min:
|
||||
temperature = temperature_min
|
||||
elif temperature > temperature_max:
|
||||
temperature = temperature_max
|
||||
|
||||
return temperature / 100
|
||||
|
||||
def _compensate_pressure(self, adc_P: int) -> float:
|
||||
"""
|
||||
Output value of “96386.0” equals 96386 Pa = 963.86 hPa
|
||||
|
||||
See the 32-bit integer implementation in the data sheet, section 4.2.3
|
||||
And the reference library:
|
||||
https://github.com/BoschSensortec/BME280_driver/blob/bme280_v3.3.4/bme280.c#L1059
|
||||
|
||||
Note that there's a 64-bit version of this function in the reference
|
||||
library on line 1016 that we're leaving unimplemented.
|
||||
"""
|
||||
pressure_min = 30000
|
||||
pressure_max = 110000
|
||||
|
||||
var1 = (self.cal_t_fine // 2) - 64000
|
||||
|
||||
var2 = (((var1 // 4) * (var1 // 4)) // 2048) * self.cal_dig_P6
|
||||
var2 = var2 + ((var1 * self.cal_dig_P5) * 2)
|
||||
var2 = (var2 // 4) + (self.cal_dig_P4 * 65536)
|
||||
|
||||
var3 = (self.cal_dig_P3 * (((var1 // 4) * (var1 // 4)) // 8192)) // 8
|
||||
|
||||
var4 = (self.cal_dig_P2 * var1) // 2
|
||||
|
||||
var1 = (var3 + var4) // 262144
|
||||
var1 = ((32768 + var1) * self.cal_dig_P1) // 32768
|
||||
|
||||
# avoid exception caused by division by zero
|
||||
if var1:
|
||||
var5 = 1048576 - adc_P
|
||||
|
||||
pressure = (var5 - (var2 // 4096)) * 3125
|
||||
|
||||
if pressure < 0x80000000:
|
||||
pressure = (pressure << 1) // var1
|
||||
else:
|
||||
pressure = (pressure // var1) * 2
|
||||
|
||||
var1 = (self.cal_dig_P9 * (((pressure // 8) * (pressure // 8)) // 8192)) // 4096
|
||||
|
||||
var2 = (((pressure // 4)) * self.cal_dig_P8) // 8192
|
||||
|
||||
pressure = pressure + ((var1 + var2 + self.cal_dig_P7) // 16)
|
||||
|
||||
if pressure < pressure_min:
|
||||
pressure = pressure_min
|
||||
elif pressure > pressure_max:
|
||||
pressure = pressure_max
|
||||
|
||||
else:
|
||||
# Invalid case
|
||||
pressure = pressure_min
|
||||
|
||||
return pressure
|
||||
|
||||
def _compensate_humidity(self, adc_H: int) -> float:
|
||||
"""
|
||||
Output value between 0.0 and 100.0, where 100.0 is 100%RH
|
||||
|
||||
See the floating-point implementation in the reference library:
|
||||
https://github.com/BoschSensortec/BME280_driver/blob/bme280_v3.3.4/bme280.c#1108
|
||||
"""
|
||||
|
||||
humidity_max = 102400
|
||||
|
||||
var1 = self.cal_t_fine - 76800
|
||||
|
||||
var2 = adc_H * 16384
|
||||
|
||||
var3 = self.cal_dig_H4 * 1048576
|
||||
|
||||
var4 = self.cal_dig_H5 * var1
|
||||
|
||||
var5 = (((var2 - var3) - var4) + 16384) // 32768
|
||||
|
||||
var2 = (var1 * self.cal_dig_H6) // 1024
|
||||
|
||||
var3 = (var1 * self.cal_dig_H3) // 2048
|
||||
|
||||
var4 = ((var2 * (var3 + 32768)) // 1024) + 2097152
|
||||
|
||||
var2 = ((var4 * self.cal_dig_H2) + 8192) // 16384
|
||||
|
||||
var3 = var5 * var2
|
||||
|
||||
var4 = ((var3 // 32768) * (var3 // 32768)) // 128
|
||||
|
||||
var5 = var3 - ((var4 * self.cal_dig_H1) // 16)
|
||||
if var5 < 0:
|
||||
var5 = 0
|
||||
if var5 > 419430400:
|
||||
var5 = 419430400
|
||||
|
||||
humidity = var5 // 4096
|
||||
|
||||
if (humidity > humidity_max):
|
||||
humidity = humidity_max
|
||||
|
||||
return humidity / 1024
|
44
station-esp8266/lib-src/umqtt/robust.py
Normal file
44
station-esp8266/lib-src/umqtt/robust.py
Normal file
@ -0,0 +1,44 @@
|
||||
import time
|
||||
from . import simple
|
||||
|
||||
|
||||
class MQTTClient(simple.MQTTClient):
|
||||
|
||||
DELAY = 2
|
||||
DEBUG = False
|
||||
|
||||
def delay(self, i):
|
||||
time.sleep(self.DELAY)
|
||||
|
||||
def log(self, in_reconnect, e):
|
||||
if self.DEBUG:
|
||||
if in_reconnect:
|
||||
print("mqtt reconnect: %r" % e)
|
||||
else:
|
||||
print("mqtt: %r" % e)
|
||||
|
||||
def reconnect(self):
|
||||
i = 0
|
||||
while 1:
|
||||
try:
|
||||
return super().connect(False)
|
||||
except OSError as e:
|
||||
self.log(True, e)
|
||||
i += 1
|
||||
self.delay(i)
|
||||
|
||||
def publish(self, topic, msg, retain=False, qos=0):
|
||||
while 1:
|
||||
try:
|
||||
return super().publish(topic, msg, retain, qos)
|
||||
except OSError as e:
|
||||
self.log(False, e)
|
||||
self.reconnect()
|
||||
|
||||
def wait_msg(self):
|
||||
while 1:
|
||||
try:
|
||||
return super().wait_msg()
|
||||
except OSError as e:
|
||||
self.log(False, e)
|
||||
self.reconnect()
|
204
station-esp8266/lib-src/umqtt/simple.py
Normal file
204
station-esp8266/lib-src/umqtt/simple.py
Normal file
@ -0,0 +1,204 @@
|
||||
import usocket as socket
|
||||
import ustruct as struct
|
||||
from ubinascii import hexlify
|
||||
|
||||
class MQTTException(Exception):
|
||||
pass
|
||||
|
||||
class MQTTClient:
|
||||
|
||||
def __init__(self, client_id, server, port=0, user=None, password=None, keepalive=0,
|
||||
ssl=False, ssl_params={}):
|
||||
if port == 0:
|
||||
port = 8883 if ssl else 1883
|
||||
self.client_id = client_id
|
||||
self.sock = None
|
||||
self.server = server
|
||||
self.port = port
|
||||
self.ssl = ssl
|
||||
self.ssl_params = ssl_params
|
||||
self.pid = 0
|
||||
self.cb = None
|
||||
self.user = user
|
||||
self.pswd = password
|
||||
self.keepalive = keepalive
|
||||
self.lw_topic = None
|
||||
self.lw_msg = None
|
||||
self.lw_qos = 0
|
||||
self.lw_retain = False
|
||||
|
||||
def _send_str(self, s):
|
||||
self.sock.write(struct.pack("!H", len(s)))
|
||||
self.sock.write(s)
|
||||
|
||||
def _recv_len(self):
|
||||
n = 0
|
||||
sh = 0
|
||||
while 1:
|
||||
b = self.sock.read(1)[0]
|
||||
n |= (b & 0x7f) << sh
|
||||
if not b & 0x80:
|
||||
return n
|
||||
sh += 7
|
||||
|
||||
def set_callback(self, f):
|
||||
self.cb = f
|
||||
|
||||
def set_last_will(self, topic, msg, retain=False, qos=0):
|
||||
assert 0 <= qos <= 2
|
||||
assert topic
|
||||
self.lw_topic = topic
|
||||
self.lw_msg = msg
|
||||
self.lw_qos = qos
|
||||
self.lw_retain = retain
|
||||
|
||||
def connect(self, clean_session=True):
|
||||
self.sock = socket.socket()
|
||||
addr = socket.getaddrinfo(self.server, self.port)[0][-1]
|
||||
self.sock.connect(addr)
|
||||
if self.ssl:
|
||||
import ussl
|
||||
self.sock = ussl.wrap_socket(self.sock, **self.ssl_params)
|
||||
premsg = bytearray(b"\x10\0\0\0\0\0")
|
||||
msg = bytearray(b"\x04MQTT\x04\x02\0\0")
|
||||
|
||||
sz = 10 + 2 + len(self.client_id)
|
||||
msg[6] = clean_session << 1
|
||||
if self.user is not None:
|
||||
sz += 2 + len(self.user) + 2 + len(self.pswd)
|
||||
msg[6] |= 0xC0
|
||||
if self.keepalive:
|
||||
assert self.keepalive < 65536
|
||||
msg[7] |= self.keepalive >> 8
|
||||
msg[8] |= self.keepalive & 0x00FF
|
||||
if self.lw_topic:
|
||||
sz += 2 + len(self.lw_topic) + 2 + len(self.lw_msg)
|
||||
msg[6] |= 0x4 | (self.lw_qos & 0x1) << 3 | (self.lw_qos & 0x2) << 3
|
||||
msg[6] |= self.lw_retain << 5
|
||||
|
||||
i = 1
|
||||
while sz > 0x7f:
|
||||
premsg[i] = (sz & 0x7f) | 0x80
|
||||
sz >>= 7
|
||||
i += 1
|
||||
premsg[i] = sz
|
||||
|
||||
self.sock.write(premsg, i + 2)
|
||||
self.sock.write(msg)
|
||||
#print(hex(len(msg)), hexlify(msg, ":"))
|
||||
self._send_str(self.client_id)
|
||||
if self.lw_topic:
|
||||
self._send_str(self.lw_topic)
|
||||
self._send_str(self.lw_msg)
|
||||
if self.user is not None:
|
||||
self._send_str(self.user)
|
||||
self._send_str(self.pswd)
|
||||
resp = self.sock.read(4)
|
||||
assert resp[0] == 0x20 and resp[1] == 0x02
|
||||
if resp[3] != 0:
|
||||
raise MQTTException(resp[3])
|
||||
return resp[2] & 1
|
||||
|
||||
def disconnect(self):
|
||||
self.sock.write(b"\xe0\0")
|
||||
self.sock.close()
|
||||
|
||||
def ping(self):
|
||||
self.sock.write(b"\xc0\0")
|
||||
|
||||
def publish(self, topic, msg, retain=False, qos=0):
|
||||
pkt = bytearray(b"\x30\0\0\0")
|
||||
pkt[0] |= qos << 1 | retain
|
||||
sz = 2 + len(topic) + len(msg)
|
||||
if qos > 0:
|
||||
sz += 2
|
||||
assert sz < 2097152
|
||||
i = 1
|
||||
while sz > 0x7f:
|
||||
pkt[i] = (sz & 0x7f) | 0x80
|
||||
sz >>= 7
|
||||
i += 1
|
||||
pkt[i] = sz
|
||||
#print(hex(len(pkt)), hexlify(pkt, ":"))
|
||||
self.sock.write(pkt, i + 1)
|
||||
self._send_str(topic)
|
||||
if qos > 0:
|
||||
self.pid += 1
|
||||
pid = self.pid
|
||||
struct.pack_into("!H", pkt, 0, pid)
|
||||
self.sock.write(pkt, 2)
|
||||
self.sock.write(msg)
|
||||
if qos == 1:
|
||||
while 1:
|
||||
op = self.wait_msg()
|
||||
if op == 0x40:
|
||||
sz = self.sock.read(1)
|
||||
assert sz == b"\x02"
|
||||
rcv_pid = self.sock.read(2)
|
||||
rcv_pid = rcv_pid[0] << 8 | rcv_pid[1]
|
||||
if pid == rcv_pid:
|
||||
return
|
||||
elif qos == 2:
|
||||
assert 0
|
||||
|
||||
def subscribe(self, topic, qos=0):
|
||||
assert self.cb is not None, "Subscribe callback is not set"
|
||||
pkt = bytearray(b"\x82\0\0\0")
|
||||
self.pid += 1
|
||||
struct.pack_into("!BH", pkt, 1, 2 + 2 + len(topic) + 1, self.pid)
|
||||
#print(hex(len(pkt)), hexlify(pkt, ":"))
|
||||
self.sock.write(pkt)
|
||||
self._send_str(topic)
|
||||
self.sock.write(qos.to_bytes(1, "little"))
|
||||
while 1:
|
||||
op = self.wait_msg()
|
||||
if op == 0x90:
|
||||
resp = self.sock.read(4)
|
||||
#print(resp)
|
||||
assert resp[1] == pkt[2] and resp[2] == pkt[3]
|
||||
if resp[3] == 0x80:
|
||||
raise MQTTException(resp[3])
|
||||
return
|
||||
|
||||
# Wait for a single incoming MQTT message and process it.
|
||||
# Subscribed messages are delivered to a callback previously
|
||||
# set by .set_callback() method. Other (internal) MQTT
|
||||
# messages processed internally.
|
||||
def wait_msg(self):
|
||||
res = self.sock.read(1)
|
||||
self.sock.setblocking(True)
|
||||
if res is None:
|
||||
return None
|
||||
if res == b"":
|
||||
raise OSError(-1)
|
||||
if res == b"\xd0": # PINGRESP
|
||||
sz = self.sock.read(1)[0]
|
||||
assert sz == 0
|
||||
return None
|
||||
op = res[0]
|
||||
if op & 0xf0 != 0x30:
|
||||
return op
|
||||
sz = self._recv_len()
|
||||
topic_len = self.sock.read(2)
|
||||
topic_len = (topic_len[0] << 8) | topic_len[1]
|
||||
topic = self.sock.read(topic_len)
|
||||
sz -= topic_len + 2
|
||||
if op & 6:
|
||||
pid = self.sock.read(2)
|
||||
pid = pid[0] << 8 | pid[1]
|
||||
sz -= 2
|
||||
msg = self.sock.read(sz)
|
||||
self.cb(topic, msg)
|
||||
if op & 6 == 2:
|
||||
pkt = bytearray(b"\x40\x02\0\0")
|
||||
struct.pack_into("!H", pkt, 2, pid)
|
||||
self.sock.write(pkt)
|
||||
elif op & 6 == 4:
|
||||
assert 0
|
||||
|
||||
# Checks whether a pending message from server is available.
|
||||
# If not, returns immediately with None. Otherwise, does
|
||||
# the same processing as wait_msg.
|
||||
def check_msg(self):
|
||||
self.sock.setblocking(False)
|
||||
return self.wait_msg()
|
Reference in New Issue
Block a user