def __parse(sml_frame: SmlFrame) -> tp.Optional[Sample]: """ Internal helper to extract relevant information :param sml_frame: SmlFrame from parser """ sample = None for sml_mesage in sml_frame: if 'messageBody' in sml_mesage: sml_list: tp.List[SmlListEntry] = sml_mesage[ 'messageBody'].get('valList', []) for sml_entry in sml_list: if sample is None: sample = Sample() obis_code: str = sml_entry.get('objName', '') value = sml_entry.get('value', '') # Differentiate SML Messages based on whether they contain a unit if 'unit' in sml_entry: sample.channels.append( ChannelValue(obis_code, value, sml_entry.get('unit'))) else: # Determine the meter_id from OBIS code if obis_code in SmlReader.OBIS_CODES_METER_ID: sample.meter_id = value # Add Channels without unit else: sample.channels.append( ChannelValue(obis_code, value)) return sample
def __parse(response: str) -> tp.Optional[Sample]: """ Internal helper to extract relevant information :param response: decoded line """ parsed = None for ident, value, unit in re.findall(r"([\d.]+)\(([\d.]+)\*?([\w\d.]+)?\)", response): if parsed is None: parsed = Sample() if not unit and ident == "9.21": parsed.meter_id = value else: parsed.channels.append(ChannelValue(ident, float(value), unit)) return parsed
def __parse(self, response) -> Sample: """ Internal helper to extract relevant information :param sml_frame: sml data from parser """ parsed = Sample() for ident, value, unit in re.findall( r"([\d.]+)\(([\d.]+)\*?([\w\d.]+)?\)", response): if not unit: if strip(self.meter_id) in value: parsed.meter_id = value else: parsed.channels.append({ 'objName': ident, 'value': float(value), 'unit': unit }) return parsed
def __parse(self, sml_frame: tp.Union[list, dict], parsed=None) -> Sample: """ Internal helper to extract relevant information :param sml_frame: sml data from parser :param parsed: only for recursive object reference forwarding """ if parsed is None: parsed = Sample() if isinstance(sml_frame, list): for elem in sml_frame: self.__parse(elem, parsed) elif isinstance(sml_frame, dict): if 'messageBody' in sml_frame: var_list = sml_frame['messageBody'].get('valList', []) for variable in var_list: if 'unit' not in variable and strip( self.meter_id) in strip( str(variable.get('value', ''))): parsed.meter_id = variable.get('value') break if parsed.meter_id: parsed.channels.extend(var_list) return parsed
'pressure': { 'uuid': '250ca04a-02ee-4a1b-98dd-3423b21008b7', 'interval': '1h' } }, 'id': 118, 'protocol': 'BME280' } }, 'middleware': { 'type': 'volkszaehler', 'middleware_url': 'http://localhost/middleware.php' } } SAMPLE_BME = Sample() SAMPLE_BME.meter_id = '0x76' SAMPLE_BME.channels = [{ 'objName': 'TEMPERATURE', 'value': 20.0, 'unit': 'C' }, { 'objName': 'HUMIDITY', 'value': 50.0, 'unit': '%' }, { 'objName': 'PRESSURE', 'value': 1000.0, 'unit': 'hPa' }]
'pressure': { 'uuid': '250ca04a-02ee-4a1b-98dd-3423b21008b7', 'interval': '1h' } }, 'id': 118, 'protocol': 'BME280' } }, 'middleware': { 'type': 'volkszaehler', 'middleware_url': 'http://localhost/middleware.php' } } SAMPLE_BME = Sample() SAMPLE_BME.meter_id = '0x76' SAMPLE_BME.channels = [ ChannelValue('TEMPERATURE', 20.0, 'C'), ChannelValue('HUMIDITY', 50.0, '%'), ChannelValue('PRESSURE', 1000.0, 'hPa') ] SAMPLE_SML = Sample() SAMPLE_SML.meter_id = '1 EMH00 12345678' SAMPLE_SML.channels = [ChannelValue('1.8.0*255', 10000, 'kWh')] SAMPLE_PLAIN = Sample() SAMPLE_PLAIN.meter_id = '888777666' SAMPLE_PLAIN.channels = [ChannelValue('6.8', 20000, 'kWh')]
def poll(self) -> tp.Optional[Sample]: """ Poll device :return: True, if successful """ # pylint: disable=too-many-locals, too-many-statements sample = Sample() with self.__I2C_LOCK: try: bus = smbus.SMBus(1) # Register Addresses reg_data = 0xF7 reg_control = 0xF4 reg_control_hum = 0xF2 # Oversample setting - page 27 oversample_temp = 2 oversample_pres = 2 mode = 1 # Oversample setting for humidity register - page 26 oversample_hum = 2 bus.write_byte_data(self.meter_id, reg_control_hum, oversample_hum) control = oversample_temp << 5 | oversample_pres << 2 | mode bus.write_byte_data(self.meter_id, reg_control, control) # Read blocks of calibration data from EEPROM # See Page 22 data sheet cal1 = bus.read_i2c_block_data(self.meter_id, 0x88, 24) cal2 = bus.read_i2c_block_data(self.meter_id, 0xA1, 1) cal3 = bus.read_i2c_block_data(self.meter_id, 0xE1, 7) dig = COMP_PARAM_STRUCT.parse( bytes(cal1) + bytes(cal2) + bytes(cal3)) dig2 = AUX_STRUCT.parse( bytes([ dig.bitfield.n1 << 4 | dig.bitfield.n2, dig.bitfield.n4 << 4 | dig.bitfield.n5, dig.bitfield.n6 << 4 | dig.bitfield.n3 ])) # Wait in ms (Datasheet Appendix B: Measurement time and current calculation) wait_time = 1.25 + 2.3 * oversample_temp + 2.3 * oversample_pres + 0.575 \ + 2.3 * oversample_hum + 0.575 time.sleep(wait_time / 1000) # Wait the required time # Read temperature/pressure/humidity data = bus.read_i2c_block_data(self.meter_id, reg_data, 8) bus.close() pres_raw = (data[0] << 12) | (data[1] << 4) | (data[2] >> 4) temp_raw = (data[3] << 12) | (data[4] << 4) | (data[5] >> 4) hum_raw = (data[6] << 8) | data[7] # Refine temperature var1 = (((temp_raw >> 3) - (dig['T1'] << 1)) * dig['T2']) >> 11 var2 = (((((temp_raw >> 4) - dig['T1']) * ( (temp_raw >> 4) - dig['T1'])) >> 12) * dig['T3']) >> 14 t_fine = var1 + var2 temperature = float(((t_fine * 5) + 128) >> 8) sample.channels.append({ 'objName': 'TEMPERATURE', 'value': temperature / 100.0, 'unit': '°C' }) # Refine pressure and adjust for temperature var1 = t_fine / 2.0 - 64000.0 var2 = var1 * var1 * dig['P6'] / 32768.0 var2 = var2 + var1 * dig['P5'] * 2.0 var2 = var2 / 4.0 + dig['P4'] * 65536.0 var1 = (dig['P3'] * var1 * var1 / 524288.0 + dig['P2'] * var1) / 524288.0 var1 = (1.0 + var1 / 32768.0) * dig['P1'] if var1 == 0: pressure = 0 else: pressure = 1048576.0 - pres_raw pressure = ((pressure - var2 / 4096.0) * 6250.0) / var1 var1 = dig['P9'] * pressure * pressure / 2147483648.0 var2 = pressure * dig['P8'] / 32768.0 pressure = pressure + (var1 + var2 + dig['P7']) / 16.0 sample.channels.append({ 'objName': 'PRESSURE', 'value': pressure / 100.0, 'unit': 'hPa' }) # Refine humidity humidity = t_fine - 76800.0 humidity = ( (hum_raw - (dig2['H4'] * 64.0 + dig2['H5'] / 16384.0 * humidity)) * (dig['H2'] / 65536.0 * (1.0 + dig['H6'] / 67108864.0 * humidity * (1.0 + dig['H3'] / 67108864.0 * humidity)))) humidity = humidity * (1.0 - dig['H1'] * humidity / 524288.0) if humidity > 100: humidity = 100 elif humidity < 0: humidity = 0 sample.channels.append({ 'objName': 'HUMIDITY', 'value': humidity, 'unit': '%' }) except OSError as err: if isinstance(err, PermissionError): error( "Bme280Reader: Insufficient permissions to access I2C bus." ) else: error( f"Bme280Reader: Cannot detect BME280 at add {self.meter_id:02x}" ) return sample
def _fetch_untracked(self) -> tp.Optional[Sample]: # pylint: disable=too-many-locals try: with Bme280Reader.I2C_BUS_LOCK: with SMBus(self.i2c_bus) as bus: # Read Chip ID chip_id = bus.read_byte_data(self.i2c_address, Bme280Reader.REG_ADDR_CHIP_ID) if self.__calibration_data is None or not self.cache_calibration: self.__calibration_data = self.__read_calibration_data(bus) else: logger.debug("Using cached calibration data") calibration_data = self.__calibration_data # Reconfigure sensor if self.__reconfiguration_required or self.mode is Bme280SensorMode.FORCED: # Reset sensor to sleep mode for reconfiguration self.__reset(bus) logger.debug("Reconfiguring sensor") # Configure humidity self.__set_register_ctrl_hum(bus, self.humidity_oversampling) # Configure other measurement parameters self.__set_register_config(bus, self.standby_time, self.irr_filter_coefficient) # Activate configuration self.__set_register_ctrl_meas(bus, self.mode, self.temperature_oversampling, self.pressure_oversampling) self.__reconfiguration_required = False # Wait for the measurement if running in forced mode if self.mode is Bme280SensorMode.FORCED: logger.debug("Waiting for measurement to complete in forced mode") osrs_t_time = 2.3 * self.temperature_oversampling osrs_p_time = 2.3 * self.pressure_oversampling + 0.575 osrs_h_time = 2.3 * self.humidity_oversampling + 0.575 measurement_time = 1.25 + osrs_t_time + osrs_p_time + osrs_h_time # Wait for measurement to complete time.sleep(measurement_time / 1000) # Read measuring status measuring, _ = self.__read_status(bus) if measuring: logger.error("Measurement is still in progress after maximum measurement time! Aborting...") return None # Read measurement registers logger.debug("Reading measurement registers") measurement = bus.read_i2c_block_data(self.i2c_address, Bme280Reader.REG_ADDR_MEASUREMENT_START, 8) # Parse measurement logger.debug("Parsing measurement") measurement_container = Bme280Reader.STRUCT_MEASUREMENT.parse(bytes(measurement)) # Calculate fine temperature to enable temperature compensation for the other measurements fine_temperature = Bme280Reader.calculate_fine_temperature(calibration_data, measurement_container.temp_raw) # Calculate measurement results temperature = Bme280Reader.calculate_temperature(fine_temperature) pressure = Bme280Reader.calculate_pressure(calibration_data, measurement_container.press_raw, fine_temperature) humidity = Bme280Reader.calculate_humidity(calibration_data, measurement_container.hum_raw, fine_temperature) # Determine meter_id meter_id = Bme280Reader.derive_meter_id(calibration_data, chip_id) # Return Sample return Sample(meter_id=meter_id, channels=[ChannelValue('TEMPERATURE', temperature, '°C'), ChannelValue('PRESSURE', pressure, 'Pa'), ChannelValue('HUMIDITY', humidity, '%')]) except OSError as err: logger.error(f"Accessing the smbus faild: {err}") except ConstructError as err: logger.error(f"Parsing the binary data failed: {err}") return None