From a6343c417754cc792f4de8ea610c2ec74e516197 Mon Sep 17 00:00:00 2001 From: flashmark <15078748+flashmark@users.noreply.github.com> Date: Wed, 6 Dec 2023 22:47:53 +0100 Subject: [PATCH 01/10] Initial plugin structure --- src/_P164_gases_ens160.ino | 170 +++++ src/src/PluginStructs/P164_data_struct.cpp | 721 +++++++++++++++++++++ src/src/PluginStructs/P164_data_struct.h | 117 ++++ 3 files changed, 1008 insertions(+) create mode 100644 src/_P164_gases_ens160.ino create mode 100644 src/src/PluginStructs/P164_data_struct.cpp create mode 100644 src/src/PluginStructs/P164_data_struct.h diff --git a/src/_P164_gases_ens160.ino b/src/_P164_gases_ens160.ino new file mode 100644 index 0000000000..04d553fb34 --- /dev/null +++ b/src/_P164_gases_ens160.ino @@ -0,0 +1,170 @@ +#include "_Plugin_Helper.h" +#ifdef USES_P164 + +// ####################################################################################################### +// #################################### Plugin-164: Gases - ENS160 (tvoc,eco2) ########################### +// ####################################################################################################### + +# include "src/PluginStructs/P164_data_struct.h" + +# define PLUGIN_164 +# define PLUGIN_ID_164 164 +# define PLUGIN_NAME_164 "Gases - ENS160" +# define PLUGIN_VALUENAME1_164 "TVOC" +# define PLUGIN_VALUENAME2_164 "eCO2" + +boolean Plugin_164(uint8_t function, struct EventStruct *event, String& string) +{ + boolean success = false; + + switch (function) + { + case PLUGIN_DEVICE_ADD: + { + Device[++deviceCount].Number = PLUGIN_ID_164; + Device[deviceCount].Type = DEVICE_TYPE_I2C; + Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_DUAL; + Device[deviceCount].Ports = 0; + Device[deviceCount].PullUpOption = false; + Device[deviceCount].InverseLogicOption = false; + Device[deviceCount].FormulaOption = true; + Device[deviceCount].ValueCount = 2; + Device[deviceCount].SendDataOption = true; + Device[deviceCount].TimerOption = true; + Device[deviceCount].GlobalSyncOption = true; + Device[deviceCount].PluginStats = true; + Device[deviceCount].I2CNoDeviceCheck = true; //TODO + break; + } + + case PLUGIN_GET_DEVICENAME: + { + string = F(PLUGIN_NAME_164); + break; + } + + case PLUGIN_GET_DEVICEVALUENAMES: + { + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_164)); + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_164)); + break; + } + + case PLUGIN_I2C_HAS_ADDRESS: + case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: + { + const uint8_t i2cAddressValues[] = { ENS160_I2CADDR_0, ENS160_I2CADDR_1 }; + constexpr int nrAddressOptions = NR_ELEMENTS(i2cAddressValues); + + if (function == PLUGIN_WEBFORM_SHOW_I2C_PARAMS) { + addFormSelectorI2C(F("i2c_addr"), nrAddressOptions, i2cAddressValues, P164_I2C_ADDR); + addFormNote(F("ADDR Low=0x52, High=0x53")); + Serial.print("ENS16x: plugin_webform_show_i2c_params() = "); + Serial.println(success); + } else { + success = intArrayContains(nrAddressOptions, i2cAddressValues, event->Par1); + Serial.print("ENS16x: plugin_i2c_has_address() = "); + Serial.println(success); + } + + break; + } + + # if FEATURE_I2C_GET_ADDRESS + case PLUGIN_I2C_GET_ADDRESS: + { + event->Par1 = P164_I2C_ADDR; + success = true; + Serial.print("ENS16x: plugin_i2c_get_addr() = "); + Serial.println(success); + break; + } + # endif // if FEATURE_I2C_GET_ADDRESS + + case PLUGIN_SET_DEFAULTS: + { + P164_I2C_ADDR = ENS160_I2CADDR_1; + success = true; + Serial.print("ENS16x: plugin_set_defaults() = "); + Serial.println(success); + break; + } + + case PLUGIN_INIT: + { + initPluginTaskData(event->TaskIndex, new (std::nothrow) P164_data_struct(event)); + P164_data_struct *P164_data = static_cast(getPluginTaskData(event->TaskIndex)); + + success = (nullptr != P164_data && P164_data->begin()); + Serial.print("ENS16x: plugin_init() = "); + Serial.println(success); + break; + } + + case PLUGIN_READ: + { + P164_data_struct *P164_data = static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr == P164_data) { + Serial.print("ENS16x: plugin_read NULLPTR"); + break; + } + + success = P164_data->read(UserVar[event->BaseVarIndex], UserVar[event->BaseVarIndex + 1]); + Serial.print("ENS16x: plugin_read() = "); + Serial.print(success); + Serial.print(" val1= "); + Serial.print(UserVar[event->BaseVarIndex]); + Serial.print(" val2= "); + Serial.println(UserVar[event->BaseVarIndex+1]); + success = true; + break; + } + + case PLUGIN_WEBFORM_LOAD: + { + success = P164_data_struct::webformLoad(event); + Serial.print("ENS16x: plugin_webform_load() = "); + Serial.println(success); + success = true; + break; + } + + case PLUGIN_WEBFORM_SAVE: + { + success = P164_data_struct::webformSave(event); + Serial.print(F("ENS16x: plugin_webform_save() = ")); + Serial.println(success); + Serial.print(F("ENS16x: I2C_ADDR = 0x")); + Serial.println(P164_I2C_ADDR, HEX); + success = true; + break; + } + + case PLUGIN_TEN_PER_SECOND: + { + P164_data_struct *P164_data = + static_cast(getPluginTaskData(event->TaskIndex)); + if (nullptr == P164_data) { + break; + } + success = P164_data->tenPerSecond(event); + success = true; + } + + case PLUGIN_FIFTY_PER_SECOND: + case PLUGIN_ONCE_A_SECOND: + { + break; + } + default: + { + // Serial.print(F("ENS160: Unhandled plugin call: ")); + // Serial.println(function); + break; + } + } + return success; +} + +#endif // ifdef USES_P164 diff --git a/src/src/PluginStructs/P164_data_struct.cpp b/src/src/PluginStructs/P164_data_struct.cpp new file mode 100644 index 0000000000..ec4d5ab83d --- /dev/null +++ b/src/src/PluginStructs/P164_data_struct.cpp @@ -0,0 +1,721 @@ +#include "../PluginStructs/P164_data_struct.h" + +#ifdef USES_P164 +#include "../Pluginstructs/P164_ENS160.h" +//#include "math.h" + +#define P164_ENS160_DEBUG + +// Use a state machine to avoid blocking the CPU while waiting for the response +#define ENS160_STATE_INITIAL 0 // Device is in an unknown state, typically after reset +#define ENS160_STATE_ERROR 1 // Device is in an error state +#define ENS160_STATE_RESETTING 2 // Waiting for response after reset +#define ENS160_STATE_IDLE 3 // Device is brought into IDLE mode +#define ENS160_STATE_DEEPSLEEP 4 // Device is brought into DEEPSLEEP mode +#define ENS160_STATE_OPERATIONAL 5 // Device is brought into OPERATIONAL mode + + +/////////////////////////////////////////////////////////////////////////////// +// Constructor // +/////////////////////////////////////////////////////////////////////////////// +P164_data_struct::P164_data_struct(struct EventStruct *event) : + i2cAddress(P164_I2C_ADDR) +{ + Serial.println(F("ENS160: Constructor")); + Serial.print("ENS160: I2C address ="); + Serial.println(i2cAddress, HEX); +} + +/////////////////////////////////////////////////////////////////////////////// +// Initialization of the connected device // +/////////////////////////////////////////////////////////////////////////////// +bool P164_data_struct::begin() +{ + if (!start(i2cAddress)) { + Serial.println(F("ENS160: P164_data_struct::begin() ***FAILED***")); + return false; + } + setMode(ENS160_OPMODE_STD); + initialized = true; + Serial.println(F("ENS160: P164_data_struct::begin() success")); + return true; +} + +/////////////////////////////////////////////////////////////////////////////// +// Fetch the processed device values as stored in the software object // +/////////////////////////////////////////////////////////////////////////////// +bool P164_data_struct::read(float& tvoc, float& eco2) +{ + if (measure()) { + return false; + } + + tvoc = getTVOC(); + eco2 = geteCO2(); + + return true; +} + +/////////////////////////////////////////////////////////////////////////////// +// Implementation of plugin PLUGIN_WEBFORM_LOAD call // +// Note: this is not a class function, only data from event is available // +/////////////////////////////////////////////////////////////////////////////// +bool P164_data_struct::webformLoad(struct EventStruct *event) +{ + bool found = false; // A chip has responded at the I2C address + uint8 reg0, reg1; + uint16_t chipID = 0; + + addRowLabel(F("Detected Sensor Type")); + + chipID = I2C_read16_LE_reg(P164_I2C_ADDR, ENS160_REG_PART_ID, &found); + + if (!found) { + addHtml(F("No device found")); + } else if (chipID == ENS160_PARTID) { + addHtml(F("ENS160")); + } else if (chipID == ENS161_PARTID) { + addHtml(F("ENS161")); + } else { + addHtmlInt(chipID); + } + + return true; +} + +/////////////////////////////////////////////////////////////////////////////// +/// Implementation of plugin PLUGIN_WEBFORM_SAVE call // +// Note: this is not a class function, only data from event is available // +/////////////////////////////////////////////////////////////////////////////// +bool P164_data_struct::webformSave(struct EventStruct *event) +{ + P164_I2C_ADDR = getFormItemInt(F("i2c_addr")); + return true; +} + +/////////////////////////////////////////////////////////////////////////////// +// Implementation of plugin PLUGIN_TEN_PER_SECOND call // +/////////////////////////////////////////////////////////////////////////////// +bool P164_data_struct::tenPerSecond(struct EventStruct *event) +{ + return evaluateState(); +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// **** The sensor handling code **** // +// This is based upon XXXXX code on github // +// The code is heavily adapted to fit the ESPEasy structures // +/////////////////////////////////////////////////////////////////////////////// + +/* + For documentation see https://www.sciosense.com/wp-content/uploads/documents/SC-001224-DS-9-ENS160-Datasheet.pdf + based on application note "ENS160 Software Integration.pdf" rev 0.01 + original code from Github TODO: + */ + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +void printState(int state); + +/////////////////////////////////////////////////////// +// Evaluate the statemachine and determine next step // +/////////////////////////////////////////////////////// +bool P164_data_struct::evaluateState() +{ + bool success = true; + int newState = this->_state; // Determine next state + + switch (this->_state) { + case ENS160_STATE_INITIAL: + // Waiting for external call to begin() + this->_available = false; + success = true; + break; + case ENS160_STATE_ERROR: + // Stay here until external retry attempt + this->_available = false; + success = true; + break; + case ENS160_STATE_RESETTING: + this->_available = false; + + // Once device has rebooted read some stuff from it and move to idle + if (timePassedSince(this->_lastChange) > ENS160_BOOTING) { // Wait until device has rebooted + this->checkPartID(); + this->writeMode(ENS160_OPMODE_IDLE); + this->clearCommand(); + this->getFirmware(); + this->getStatus(); + newState = ENS160_STATE_IDLE; + } + break; + case ENS160_STATE_IDLE: + // Set device into desired operation mode as requested through _opmode + this->_available = true; + + switch (this->_opmode) { + case ENS160_OPMODE_STD: + this->writeMode(ENS160_OPMODE_STD); + newState = ENS160_STATE_OPERATIONAL; + break; + case ENS160_OPMODE_LP: + this->writeMode(ENS160_OPMODE_LP); + newState = ENS160_STATE_OPERATIONAL; + break; + case ENS160_OPMODE_ULP: + this->writeMode(ENS160_OPMODE_ULP); + newState = ENS160_STATE_OPERATIONAL; + break; + case ENS160_OPMODE_RESET: + this->writeMode(ENS160_OPMODE_RESET); + this->_opmode = ENS160_OPMODE_IDLE; // Prevent reset loop + newState = ENS160_STATE_RESETTING; + break; + } + break; + case ENS160_STATE_DEEPSLEEP: + // Device is put to DEEPSLEEP mode. If requested move to another mode. But alsways through IDLE + this->_available = true; + + if (this->_opmode != ENS160_OPMODE_DEEP_SLEEP) { + this->writeMode(ENS160_OPMODE_IDLE); // Move through Idle state + newState = ENS160_STATE_IDLE; + } + break; + case ENS160_STATE_OPERATIONAL: + // Device is in one of the operational modes + this->_available = true; + + switch (this->_opmode) { + case ENS160_OPMODE_DEEP_SLEEP: + case ENS160_STATE_IDLE: + case ENS160_OPMODE_RESET: + this->writeMode(ENS160_OPMODE_IDLE); // Move through Idle state + newState = ENS160_STATE_IDLE; + break; + } + break; + default: + // Unplanned state, force into error state + newState = ENS160_STATE_ERROR; + break; + } + + if (newState != this->_state) { + this->_state = newState; + this->_lastChange = millis(); + #ifdef P164_ENS160_DEBUG + Serial.print(F("ENS16x: State transition:")); + printState(newState); + Serial.print(F("; opmode ")); + Serial.print(this->_opmode); + Serial.println(F(".")); + #endif // ifdef P164_ENS160_DEBUG + } + else { + #ifdef P164_ENS160_DEBUG + // Serial.print(F("ENS16x: state ")); + // printState(newState); + // Serial.print(F(" opmode ")); + // Serial.println(this->_opmode); + #endif + } + + return success; +} + +/////////////////////////////////////////////////////////////////// +// Init I2C communication, resets ENS160 and checks its PART_ID. // +// Returns false on I2C problems or wrong PART_ID. // +/////////////////////////////////////////////////////////////////// +bool P164_data_struct::start(uint8_t slaveaddr) +{ + uint8_t result = 0; + + // Initialize internal bookkeeping; + this->_state = ENS160_STATE_INITIAL; // Assume nothing, start clean + this->_lastChange = millis(); // Bookmark last state change as now + this->_available = false; + this->_opmode = ENS160_OPMODE_STD; + + // Set IO pin levels + if (this->_ADDR > 0) { + pinMode(this->_ADDR, OUTPUT); // ADDR is input pin for device + digitalWrite(this->_ADDR, LOW); // Set it identify ENS160_I2CADDR_0 + } + + if (this->_nINT > 0) { + pinMode(this->_nINT, INPUT_PULLUP); // INT is open drain output pin for the device + } + + if (this->_nCS > 0) { + pinMode(this->_nCS, OUTPUT); // CS is input for the device + digitalWrite(this->_nCS, HIGH); // Must be HIGH for I2C operation + } + + result = this->writeMode(ENS160_OPMODE_RESET); + + #ifdef P164_ENS160_DEBUG + Serial.print(F("ENS16x: reset() result: ")); + Serial.println(result == 0 ? F("ok") : F("nok")); + #endif // ifdef P164_ENS160_DEBUG + this->moveToState(ENS160_STATE_RESETTING); // Go to next state RESETTING + + return true; +} + +/////////////////////////////////////////////////////////////////////////////// +// Initialize definition of custom mode with steps // +// TODO: Undocumented feature, to be reworked for ESPEasy version // +/////////////////////////////////////////////////////////////////////////////// +bool P164_data_struct::initCustomMode(uint16_t stepNum) { + uint8_t result; + + if (stepNum > 0) { + this->_stepCount = stepNum; + result = this->writeMode(ENS160_OPMODE_IDLE); + result = this->clearCommand(); + result = P164_data_struct::write8(i2cAddress, ENS160_REG_COMMAND, ENS160_COMMAND_SETSEQ); + } else { + result = 1; + } + + // TODO check if delay is needed + // delay(ENS160_BOOTING); // Wait to boot after reset + + return result == 0; +} + +/////////////////////////////////////////////////////////////////////////////// +// Add a step to custom measurement profile with definition of duration // +// enabled data acquisition and temperature for each hotplate // +// TODO: Undocumented feature, to be reworked for ESPEasy version // +/////////////////////////////////////////////////////////////////////////////// +bool P164_data_struct::addCustomStep(uint16_t time, bool measureHP0, bool measureHP1, + bool measureHP2, bool measureHP3, uint16_t tempHP0, + uint16_t tempHP1, uint16_t tempHP2, uint16_t tempHP3) { + uint8_t seq_ack; + uint8_t temp; + + #ifdef P164_ENS160_DEBUG + Serial.print("setCustomMode() write step "); + Serial.println(this->_stepCount); + #endif // ifdef P164_ENS160_DEBUG + + // TODO check if delay is needed + // delay(ENS160_BOOTING); // Wait to boot after reset + + temp = (uint8_t)(((time / 24) - 1) << 6); + + if (measureHP0) { temp = temp | 0x20; } + + if (measureHP1) { temp = temp | 0x10; } + + if (measureHP2) { temp = temp | 0x8; } + + if (measureHP3) { temp = temp | 0x4; } + P164_data_struct::write8(i2cAddress, ENS160_REG_GPR_WRITE_0, temp); + + temp = (uint8_t)(((time / 24) - 1) >> 2); + P164_data_struct::write8(i2cAddress, ENS160_REG_GPR_WRITE_1, temp); + + P164_data_struct::write8(i2cAddress, ENS160_REG_GPR_WRITE_2, (uint8_t)(tempHP0 / 2)); + P164_data_struct::write8(i2cAddress, ENS160_REG_GPR_WRITE_3, (uint8_t)(tempHP1 / 2)); + P164_data_struct::write8(i2cAddress, ENS160_REG_GPR_WRITE_4, (uint8_t)(tempHP2 / 2)); + P164_data_struct::write8(i2cAddress, ENS160_REG_GPR_WRITE_5, (uint8_t)(tempHP3 / 2)); + + P164_data_struct::write8(i2cAddress, ENS160_REG_GPR_WRITE_6, (uint8_t)(this->_stepCount - 1)); + + if (this->_stepCount == 1) { + P164_data_struct::write8(i2cAddress, ENS160_REG_GPR_WRITE_7, 128); + } else { + P164_data_struct::write8(i2cAddress, ENS160_REG_GPR_WRITE_7, 0); + } + + // TODO check if delay is needed + // delay(ENS160_BOOTING); + + seq_ack = P164_data_struct::read8(i2cAddress, ENS160_REG_GPR_READ_7); + + // TODO check if delay is needed + // delay(ENS160_BOOTING); // Wait to boot after reset + + if ((ENS160_SEQ_ACK_COMPLETE | this->_stepCount) != seq_ack) { + this->_stepCount = this->_stepCount - 1; + return 0; + } else { + return 1; + } +} + +/////////////////////////////////////////////////////////////////////////// +// Perform prediction measurement and store result in internal variables // +// Return: true if data is fresh (first reading of new data) // +/////////////////////////////////////////////////////////////////////////// +bool P164_data_struct::measure() { + uint8_t i2cbuf[8]; + uint8_t status; + bool newData = false; + + #ifdef P164_ENS160_DEBUG + Serial.println(F("ENS16x: Start measurement")); + #endif // ifdef P164_ENS160_DEBUG + + if (this->_state == ENS160_STATE_OPERATIONAL) { + // Check if new data is aquired + status = P164_data_struct::read8(i2cAddress, ENS160_REG_DATA_STATUS); + + // Read predictions + if (IS_NEWDAT(status)) { + newData = true; + P164_data_struct::read(i2cAddress, ENS160_REG_DATA_AQI, i2cbuf, 7); + _data_aqi = i2cbuf[0]; + _data_tvoc = i2cbuf[1] | ((uint16_t)i2cbuf[2] << 8); + _data_eco2 = i2cbuf[3] | ((uint16_t)i2cbuf[4] << 8); + + if (_revENS16x > 0) { + _data_aqi500 = ((uint16_t)i2cbuf[5]) | ((uint16_t)i2cbuf[6] << 8); + } + else { + _data_aqi500 = 0; + } + } + } + + #ifdef P164_ENS160_DEBUG + Serial.print(F("ENS16x: measure: aqi= ")); + Serial.print(_data_aqi); + Serial.print(F(" tvoc= ")); + Serial.print(_data_tvoc); + Serial.print(F(" eco2= ")); + Serial.print(_data_eco2); + Serial.println(F(".")); + #endif // ifdef P164_ENS160_DEBUG + + return newData; +} + +//////////////////////////////////////////////////////////////////// +// Perfrom raw measurement and store result in internal variables // +//////////////////////////////////////////////////////////////////// +bool P164_data_struct::measureRaw() { + uint8_t i2cbuf[8]; + uint8_t status; + bool newData = false; + + // Set default status for early bail out + #ifdef P164_ENS160_DEBUG + Serial.println("ENS16x: Start measurement"); + #endif // ifdef P164_ENS160_DEBUG + + if (this->_state == ENS160_STATE_OPERATIONAL) { + status = P164_data_struct::read8(i2cAddress, ENS160_REG_DATA_STATUS); + + if (IS_NEWGPR(status)) { + newData = true; + + // Read raw resistance values + P164_data_struct::read(i2cAddress, ENS160_REG_GPR_READ_0, i2cbuf, 8); + _hp0_rs = CONVERT_RS_RAW2OHMS_F((uint32_t)(i2cbuf[0] | ((uint16_t)i2cbuf[1] << 8))); + _hp1_rs = CONVERT_RS_RAW2OHMS_F((uint32_t)(i2cbuf[2] | ((uint16_t)i2cbuf[3] << 8))); + _hp2_rs = CONVERT_RS_RAW2OHMS_F((uint32_t)(i2cbuf[4] | ((uint16_t)i2cbuf[5] << 8))); + _hp3_rs = CONVERT_RS_RAW2OHMS_F((uint32_t)(i2cbuf[6] | ((uint16_t)i2cbuf[7] << 8))); + + // Read baselines + P164_data_struct::read(i2cAddress, ENS160_REG_DATA_BL, i2cbuf, 8); + _hp0_bl = CONVERT_RS_RAW2OHMS_F((uint32_t)(i2cbuf[0] | ((uint16_t)i2cbuf[1] << 8))); + _hp1_bl = CONVERT_RS_RAW2OHMS_F((uint32_t)(i2cbuf[2] | ((uint16_t)i2cbuf[3] << 8))); + _hp2_bl = CONVERT_RS_RAW2OHMS_F((uint32_t)(i2cbuf[4] | ((uint16_t)i2cbuf[5] << 8))); + _hp3_bl = CONVERT_RS_RAW2OHMS_F((uint32_t)(i2cbuf[6] | ((uint16_t)i2cbuf[7] << 8))); + + P164_data_struct::read(i2cAddress, ENS160_REG_DATA_MISR, i2cbuf, 1); + _misr = i2cbuf[0]; + } + } + + return newData; +} + +/////////////////////////////////////////////////////////////////////////////// +// Writes t (degC) and h (%rh) to ENV_DATA. Returns false on I2C problems. // +/////////////////////////////////////////////////////////////////////////////// +bool P164_data_struct::set_envdata(float t, float h) { + uint16_t t_data = (uint16_t)((t + 273.15f) * 64.0f); + uint16_t rh_data = (uint16_t)(h * 512.0f); + + return this->set_envdata210(t_data, rh_data); +} + +/////////////////////////////////////////////////////////////////////////////// +// Writes t and h (in ENS210 format) to ENV_DATA. Returns false on I2C problems. +/////////////////////////////////////////////////////////////////////////////// +bool P164_data_struct::set_envdata210(uint16_t t, uint16_t h) { + uint8_t trh_in[4]; // Buffer for I2C registers TEMP_IN + RH_IN + + // temp = (uint16_t)((t + 273.15f) * 64.0f); + trh_in[0] = t & 0xff; // TEMP_IN LSB + trh_in[1] = (t >> 8) & 0xff; // TEMP_IN MSB + + // temp = (uint16_t)(h * 512.0f); + trh_in[2] = h & 0xff; // RH_IN LSB + trh_in[3] = (h >> 8) & 0xff; // RH_IN MSB + + uint8_t result = P164_data_struct::write(i2cAddress, ENS160_REG_TEMP_IN, trh_in, 4); + + return result; +} + +/////////////////////////////////////////////////////////////////////////////// +// Helper function to enter a new state // +/////////////////////////////////////////////////////////////////////////////// +void P164_data_struct::moveToState(int newState) +{ + this->_state = newState; // Enter the new state + this->_lastChange = millis(); // Mark time of transition + this->evaluateState(); // Check if we can already move on +} + +/////////////////////////////////////////////////////////////////////////////// +// Read firmware revision // +// Precondition: Device MODE is IDLE // +/////////////////////////////////////////////////////////////////////////////// +bool P164_data_struct::getFirmware() { + uint8_t i2cbuf[3]; + uint8_t result; + + result = P164_data_struct::write8(i2cAddress, ENS160_REG_COMMAND, ENS160_COMMAND_GET_APPVER); + + if (result == 0) { + result = P164_data_struct::read(i2cAddress, ENS160_REG_GPR_READ_4, i2cbuf, 3); + } + + if (result == 0) { + this->_fw_ver_major = i2cbuf[0]; + this->_fw_ver_minor = i2cbuf[1]; + this->_fw_ver_build = i2cbuf[2]; + } + else { + this->_fw_ver_major = 0; + this->_fw_ver_minor = 0; + this->_fw_ver_build = 0; + } + + #ifdef P164_ENS160_DEBUG + Serial.print(F("ENS16x: getFirmware() result: ")); + Serial.print(result == 0 ? "ok," : "nok,"); + Serial.print(this->_fw_ver_major); + Serial.print(F(",")); + Serial.println(this->_fw_ver_minor); + Serial.print(F(",")); + Serial.println(this->_fw_ver_build); + #endif // ifdef P164_ENS160_DEBUG + + return result == 0; +} + +////////////////////////////////// +// Set operation mode of sensor // +////////////////////////////////// +bool P164_data_struct::setMode(uint8_t mode) { + bool result = false; + + // LP only valid for rev>0 + if (!(mode == ENS160_OPMODE_LP) and (_revENS16x == 0)) { + this->_opmode = mode; + result = true; + } + + #ifdef P164_ENS160_DEBUG + Serial.print(F("ENS16x: setMode(")); + Serial.print(mode); + Serial.println(F(")")); + #endif // ifdef P164_ENS160_DEBUG + + return result; +} + +/////////////////////////////////////////////////////////////////////////////// +// Write to opmode register of the device // +/////////////////////////////////////////////////////////////////////////////// +bool P164_data_struct::writeMode(uint8_t mode) { + uint8_t result; + + result = P164_data_struct::write8(i2cAddress, ENS160_REG_OPMODE, mode); + + #ifdef P164_ENS160_DEBUG + Serial.print(F("ENS16x: writeMode() activate result: ")); + Serial.println(result == 0 ? F("ok") : F("nok")); + #endif // ifdef P164_ENS160_DEBUG + + return result == 0; +} + +/////////////////////////////////////////////////////////////////////////////// +// Read the part ID from ENS160 device and check for validity // +/////////////////////////////////////////////////////////////////////////////// +bool P164_data_struct::checkPartID(void) { + uint8_t i2cbuf[2]; // Buffer for returned I2C data + uint16_t part_id; // Resulting PartID + bool result = false; + + P164_data_struct::read(i2cAddress, ENS160_REG_PART_ID, i2cbuf, 2); + part_id = i2cbuf[0] | ((uint16_t)i2cbuf[1] << 8); + + if (part_id == ENS160_PARTID) { + this->_revENS16x = 0; + result = true; + } + else if (part_id == ENS161_PARTID) { + this->_revENS16x = 1; + result = true; + } + else { + this->_revENS16x = 0xFF; + result = false; + } + + #ifdef P164_ENS160_DEBUG + Serial.print(F("ENS16x: checkPartID() result: ")); + + if (part_id == ENS160_PARTID) { Serial.println(F("ENS160 ok")); } + else if (part_id == ENS161_PARTID) { Serial.println(F("ENS161 ok")); } + else { Serial.println(F("nok")); } + #endif // ifdef P164_ENS160_DEBUG + + return result; +} + +/////////////////////////////////////////////////////////////////////////////// +// Clear any pending command in device // +/////////////////////////////////////////////////////////////////////////////// +bool P164_data_struct::clearCommand(void) { + uint8_t status; + uint8_t result; + + result = P164_data_struct::write8(i2cAddress, ENS160_REG_COMMAND, ENS160_COMMAND_NOP); + result = P164_data_struct::write8(i2cAddress, ENS160_REG_COMMAND, ENS160_COMMAND_CLRGPR); + #ifdef P164_ENS160_DEBUG + Serial.print(F("ENS16x: clearCommand() result: ")); + Serial.println(result == 0 ? "ok" : "nok"); + #endif // ifdef P164_ENS160_DEBUG + + return result == 0; +} + +/////////////////////////////////////////////////////////////////////////////// +// Read status register from device // +/////////////////////////////////////////////////////////////////////////////// +bool P164_data_struct::getStatus() +{ + this->_statusReg = P164_data_struct::read8(i2cAddress, ENS160_REG_DATA_STATUS); + #ifdef P164_ENS160_DEBUG + Serial.print(F("ENS16x: Status register: 0x")); + Serial.println(this->_statusReg, HEX); + #endif // ifdef P164_ENS160_DEBUG + return true; +} + +/////////////////////////////////////////////////////////////////////////////// +// Helper function to display the current state in readable format // +/////////////////////////////////////////////////////////////////////////////// +void printState(int state) { + #ifdef P164_ENS160_DEBUG + + switch (state) { + case ENS160_STATE_INITIAL: Serial.print(F("initial")); break; + case ENS160_STATE_ERROR: Serial.print(F("error")); break; + case ENS160_STATE_RESETTING: Serial.print(F("resetting")); break; + case ENS160_STATE_IDLE: Serial.print(F("idle")); break; + case ENS160_STATE_DEEPSLEEP: Serial.print(F("deepsleep")); break; + case ENS160_STATE_OPERATIONAL: Serial.print(F("operational")); break; + default: Serial.print(F("***ERROR***")); break; + } + #endif // ifdef P164_ENS160_DEBUG +} + +/////////////////////////////////////////////////////////////////////////////// +// TODO: Refactor I2C to use the ESPEasy standard // +/////////////////////////////////////////////////////////////////////////////// + +/**************************************************************************/ + +uint8_t P164_data_struct::read8(uint8_t addr, byte reg) { + uint8_t ret; + + (void)P164_data_struct::read(addr, reg, &ret, 1); + + return ret; +} + +bool P164_data_struct::read(uint8_t addr, uint8_t reg, uint8_t *buf, uint8_t num) { + uint8_t pos = 0; + bool result = true; + + #ifdef P164_ENS160_DEBUG + Serial.print(F("ENS16x: I2C read address: 0x")); + Serial.print(addr, HEX); + Serial.print(F(", register: 0x")); + Serial.print(reg, HEX); + Serial.print(F(" data:")); + #endif // ifdef P164_ENS160_DEBUG + + // on arduino we need to read in 32 byte chunks + while (pos < num) { + uint8_t read_now = min((uint8_t)32, (uint8_t)(num - pos)); + Wire.beginTransmission((uint8_t)addr); + + Wire.write((uint8_t)reg + pos); + result = Wire.endTransmission(); + Wire.requestFrom((uint8_t)addr, read_now); + + for (int i = 0; i < read_now; i++) { + buf[pos] = Wire.read(); + #ifdef P164_ENS160_DEBUG + Serial.print(" 0x"); + Serial.print(buf[pos], HEX); + #endif // ifdef P164_ENS160_DEBUG + pos++; + } + } + #ifdef P164_ENS160_DEBUG + Serial.println("."); + #endif // ifdef P164_ENS160_DEBUG + + return result; +} + +/**************************************************************************/ + +bool P164_data_struct::write8(uint8_t addr, byte reg, byte value) { + return P164_data_struct::write(addr, reg, &value, 1); +} + +bool P164_data_struct::write(uint8_t addr, uint8_t reg, uint8_t *buf, uint8_t num) { + uint8_t result; + + #ifdef P164_ENS160_DEBUG + Serial.print(F("ENS16x: I2C write address: 0x")); + Serial.print(addr, HEX); + Serial.print(F(", register: 0x")); + Serial.print(reg, HEX); + Serial.print(F(", value:")); + + for (int i = 0; i < num; i++) { + Serial.print(F(" 0x")); + Serial.print(buf[i], HEX); + } + Serial.println(); + #endif // ifdef P164_ENS160_DEBUG + + Wire.beginTransmission((uint8_t)addr); + Wire.write((uint8_t)reg); + Wire.write((uint8_t *)buf, num); + result = Wire.endTransmission(); + return result; +} + +/**************************************************************************/ + +#endif // ifdef USES_P164 diff --git a/src/src/PluginStructs/P164_data_struct.h b/src/src/PluginStructs/P164_data_struct.h new file mode 100644 index 0000000000..bcce4452b0 --- /dev/null +++ b/src/src/PluginStructs/P164_data_struct.h @@ -0,0 +1,117 @@ +#ifndef PLUGINSTRUCTS_P164_DATA_STRUCT_H +#define PLUGINSTRUCTS_P164_DATA_STRUCT_H + +#include "../../_Plugin_Helper.h" +#ifdef USES_P164 + +#include "../Pluginstructs/P164_ENS160.h" + +# define P164_I2C_ADDR PCONFIG(0) + +struct P164_data_struct : public PluginTaskData_base { +public: + + P164_data_struct(struct EventStruct *event); + //////P164_data_struct() = delete; + virtual ~P164_data_struct() = default; + + bool begin(); + bool read(float& tvoc, float& eco2); + static bool webformLoad(struct EventStruct *event); + static bool webformSave(struct EventStruct *event); + bool tenPerSecond(struct EventStruct *event); +private: + + uint8_t i2cAddress = ENS160_I2CADDR_0; + bool initialized = false; + + bool evaluateState(); // Evaluate state machine for next action. Should be called regularly. + + bool start(uint8_t slaveaddr); // Init I2C communication, resets ENS160 and checks its PART_ID. Returns false on I2C problems or wrong PART_ID. + bool available() { return this->_available; } // Report availability of sensor + uint8_t revENS16x() { return this->_revENS16x; } // Report version of sensor (0: ENS160, 1: ENS161) + bool setMode(uint8_t mode); // Set operation mode of sensor + + bool initCustomMode(uint16_t stepNum); // Initialize definition of custom mode with steps + bool addCustomStep(uint16_t time, bool measureHP0, bool measureHP1, bool measureHP2, bool measureHP3, uint16_t tempHP0, uint16_t tempHP1, uint16_t tempHP2, uint16_t tempHP3); + // Add a step to custom measurement profile with definition of duration, enabled data acquisition and temperature for each hotplate + + bool measure(); // Perform measurement and stores result in internal variables + bool measureRaw(); // Perform raw measurement and stores result in internal variables + bool set_envdata(float t, float h); // Writes t (degC) and h (%rh) to ENV_DATA. Returns "0" if I2C transmission is successful + bool set_envdata210(uint16_t t, uint16_t h); // Writes t and h (in ENS210 format) to ENV_DATA. Returns "0" if I2C transmission is successful + uint8_t getMajorRev() { return this->_fw_ver_major; } // Get major revision number of used firmware + uint8_t getMinorRev() { return this->_fw_ver_minor; } // Get minor revision number of used firmware + uint8_t getBuild() { return this->_fw_ver_build; } // Get build revision number of used firmware + + uint8_t getAQI() { return this->_data_aqi; } // Get AQI value of last measurement + uint16_t getTVOC() { return this->_data_tvoc; } // Get TVOC value of last measurement + uint16_t geteCO2() { return this->_data_eco2; } // Get eCO2 value of last measurement + uint16_t getAQI500() { return this->_data_aqi500; } // Get AQI500 value of last measurement + uint32_t getHP0() { return this->_hp0_rs; } // Get resistance of HP0 of last measurement + uint32_t getHP1() { return this->_hp1_rs; } // Get resistance of HP1 of last measurement + uint32_t getHP2() { return this->_hp2_rs; } // Get resistance of HP2 of last measurement + uint32_t getHP3() { return this->_hp3_rs; } // Get resistance of HP3 of last measurement + uint32_t getHP0BL() { return this->_hp0_bl; } // Get baseline resistance of HP0 of last measurement + uint32_t getHP1BL() { return this->_hp1_bl; } // Get baseline resistance of HP1 of last measurement + uint32_t getHP2BL() { return this->_hp2_bl; } // Get baseline resistance of HP2 of last measurement + uint32_t getHP3BL() { return this->_hp3_bl; } // Get baseline resistance of HP3 of last measurement + uint8_t getMISR() { return this->_misr; } // Return status code of sensor + + uint8_t _ADDR = 0; // ADDR pin number (0: not used) + uint8_t _nINT = 0; // INT pin number (0: not used) + uint8_t _nCS = 0; // CS pin number (0: not used) + + int _state = 0; // General state of the software + ulong _lastChange = 0u; // Timestamp of last state transition + uint8_t _opmode = 0; // ENS160 Mode + + bool checkPartID(); // Reads the part ID and confirms valid sensor + bool clearCommand(); // Initialize idle mode and confirms + bool getFirmware(); // Read firmware revisions + bool getStatus(); // Read status register + bool writeMode(uint8_t mode); // Write the opmode register + + bool _available = false; // ENS160 available + uint8_t _statusReg = 0; // ENS160 latest status register readout + uint8_t _revENS16x = 0; // ENS160 or ENS161 connected? (FW >7) + + uint8_t _fw_ver_major; + uint8_t _fw_ver_minor; + uint8_t _fw_ver_build; + + uint16_t _stepCount; // Counter for custom sequence + + uint8_t _data_aqi; + uint16_t _data_tvoc; + uint16_t _data_eco2; + uint16_t _data_aqi500; + uint32_t _hp0_rs; + uint32_t _hp0_bl; + uint32_t _hp1_rs; + uint32_t _hp1_bl; + uint32_t _hp2_rs; + uint32_t _hp2_bl; + uint32_t _hp3_rs; + uint32_t _hp3_bl; + uint16_t _temp; + int _slaveaddr; // Slave address of the ENS160 + uint8_t _misr; + + //Isotherm, HP0 252°C / HP1 350°C / HP2 250°C / HP3 324°C / measure every 1008ms + uint8_t _seq_steps[1][8] = { { 0x7C, 0x0A, 0x7E, 0xAF, 0xAF, 0xA2, 0x00, 0x80 }, }; + +/****************************************************************************/ +/* General functions */ +/****************************************************************************/ + void moveToState(int newState); + + static uint8_t read8(uint8_t addr, byte reg); + static bool read(uint8_t addr, uint8_t reg, uint8_t *buf, uint8_t num); + + static bool write8(uint8_t addr, byte reg, byte value); + static bool write(uint8_t addr, uint8_t reg, uint8_t *buf, uint8_t num); + +}; +#endif // ifdef USES_P164 +#endif // ifndef PLUGINSTRUCTS_P164_DATA_STRUCT_H From 1896c3c6eadc9f493c794732cfbad22deb6b7f09 Mon Sep 17 00:00:00 2001 From: flashmark <15078748+flashmark@users.noreply.github.com> Date: Sat, 9 Dec 2023 21:31:19 +0100 Subject: [PATCH 02/10] Rough, working version --- src/_P164_gases_ens160.ino | 58 +- src/src/PluginStructs/P164_data_struct.cpp | 634 +++++++++++++-------- src/src/PluginStructs/P164_data_struct.h | 27 +- 3 files changed, 442 insertions(+), 277 deletions(-) diff --git a/src/_P164_gases_ens160.ino b/src/_P164_gases_ens160.ino index 04d553fb34..63bdbc65b2 100644 --- a/src/_P164_gases_ens160.ino +++ b/src/_P164_gases_ens160.ino @@ -2,14 +2,22 @@ #ifdef USES_P164 // ####################################################################################################### -// #################################### Plugin-164: Gases - ENS160 (tvoc,eco2) ########################### +// #################################### Plugin-164: Gases - ENS16x (tvoc,eco2) ########################### +// ####################################################################################################### +// P164 "GASES - ENS16x (TVOC, eCO2)" +// Plugin for ENS160 & ENS161 TVOC and eCO2 sensor with I2C interface from ScioSense +// Based upon: https://github.com/sciosense/ENS160_driver +// For documentation see +// https://www.sciosense.com/wp-content/uploads/documents/SC-001224-DS-9-ENS160-Datasheet.pdf +// +// 2023 By flashmark // ####################################################################################################### # include "src/PluginStructs/P164_data_struct.h" # define PLUGIN_164 # define PLUGIN_ID_164 164 -# define PLUGIN_NAME_164 "Gases - ENS160" +# define PLUGIN_NAME_164 "Gases - ENS16x" # define PLUGIN_VALUENAME1_164 "TVOC" # define PLUGIN_VALUENAME2_164 "eCO2" @@ -59,13 +67,9 @@ boolean Plugin_164(uint8_t function, struct EventStruct *event, String& string) if (function == PLUGIN_WEBFORM_SHOW_I2C_PARAMS) { addFormSelectorI2C(F("i2c_addr"), nrAddressOptions, i2cAddressValues, P164_I2C_ADDR); addFormNote(F("ADDR Low=0x52, High=0x53")); - Serial.print("ENS16x: plugin_webform_show_i2c_params() = "); - Serial.println(success); } else { success = intArrayContains(nrAddressOptions, i2cAddressValues, event->Par1); - Serial.print("ENS16x: plugin_i2c_has_address() = "); - Serial.println(success); - } + } break; } @@ -75,8 +79,6 @@ boolean Plugin_164(uint8_t function, struct EventStruct *event, String& string) { event->Par1 = P164_I2C_ADDR; success = true; - Serial.print("ENS16x: plugin_i2c_get_addr() = "); - Serial.println(success); break; } # endif // if FEATURE_I2C_GET_ADDRESS @@ -85,8 +87,6 @@ boolean Plugin_164(uint8_t function, struct EventStruct *event, String& string) { P164_I2C_ADDR = ENS160_I2CADDR_1; success = true; - Serial.print("ENS16x: plugin_set_defaults() = "); - Serial.println(success); break; } @@ -96,8 +96,6 @@ boolean Plugin_164(uint8_t function, struct EventStruct *event, String& string) P164_data_struct *P164_data = static_cast(getPluginTaskData(event->TaskIndex)); success = (nullptr != P164_data && P164_data->begin()); - Serial.print("ENS16x: plugin_init() = "); - Serial.println(success); break; } @@ -106,17 +104,20 @@ boolean Plugin_164(uint8_t function, struct EventStruct *event, String& string) P164_data_struct *P164_data = static_cast(getPluginTaskData(event->TaskIndex)); if (nullptr == P164_data) { - Serial.print("ENS16x: plugin_read NULLPTR"); + Serial.print("P164: plugin_read NULLPTR"); break; } - success = P164_data->read(UserVar[event->BaseVarIndex], UserVar[event->BaseVarIndex + 1]); - Serial.print("ENS16x: plugin_read() = "); - Serial.print(success); - Serial.print(" val1= "); - Serial.print(UserVar[event->BaseVarIndex]); - Serial.print(" val2= "); - Serial.println(UserVar[event->BaseVarIndex+1]); + float temperature = 20.0f; // A reasonable value in case temperature source task is invalid + float humidity = 50.0f; // A reasonable value in case humidity source task is invalid + if (validTaskIndex(P164_PCONFIG_TEMP_TASK) && validTaskIndex(P164_PCONFIG_HUM_TASK)) + { + // we're checking a var from another task, so calculate that basevar + temperature = UserVar[P164_PCONFIG_TEMP_TASK * VARS_PER_TASK + P164_PCONFIG_TEMP_VAL]; // in degrees C + humidity = UserVar[P164_PCONFIG_HUM_TASK * VARS_PER_TASK + P164_PCONFIG_HUM_VAL]; // in % relative + } + success = P164_data->read(UserVar[event->BaseVarIndex], UserVar[event->BaseVarIndex + 1], temperature, humidity); + success = true; break; } @@ -124,20 +125,12 @@ boolean Plugin_164(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_WEBFORM_LOAD: { success = P164_data_struct::webformLoad(event); - Serial.print("ENS16x: plugin_webform_load() = "); - Serial.println(success); - success = true; break; } case PLUGIN_WEBFORM_SAVE: { success = P164_data_struct::webformSave(event); - Serial.print(F("ENS16x: plugin_webform_save() = ")); - Serial.println(success); - Serial.print(F("ENS16x: I2C_ADDR = 0x")); - Serial.println(P164_I2C_ADDR, HEX); - success = true; break; } @@ -157,12 +150,7 @@ boolean Plugin_164(uint8_t function, struct EventStruct *event, String& string) { break; } - default: - { - // Serial.print(F("ENS160: Unhandled plugin call: ")); - // Serial.println(function); - break; - } + } return success; } diff --git a/src/src/PluginStructs/P164_data_struct.cpp b/src/src/PluginStructs/P164_data_struct.cpp index ec4d5ab83d..74ed284d01 100644 --- a/src/src/PluginStructs/P164_data_struct.cpp +++ b/src/src/PluginStructs/P164_data_struct.cpp @@ -1,10 +1,18 @@ +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Plugin data structure for P164 "GASES - ENS16x (TVOC, eCO2)" +// Plugin for ENS160 & ENS161 TVOC and eCO2 sensor with I2C interface from ScioSense +// Based upon: https://github.com/sciosense/ENS160_driver +// For documentation see +// https://www.sciosense.com/wp-content/uploads/documents/SC-001224-DS-9-ENS160-Datasheet.pdf +// +// 2023 By flashmark +/////////////////////////////////////////////////////////////////////////////////////////////////// + #include "../PluginStructs/P164_data_struct.h" #ifdef USES_P164 -#include "../Pluginstructs/P164_ENS160.h" -//#include "math.h" -#define P164_ENS160_DEBUG +#define P164_ENS160_DEBUG // Enable debugging using teh serial port // Use a state machine to avoid blocking the CPU while waiting for the response #define ENS160_STATE_INITIAL 0 // Device is in an unknown state, typically after reset @@ -14,55 +22,158 @@ #define ENS160_STATE_DEEPSLEEP 4 // Device is brought into DEEPSLEEP mode #define ENS160_STATE_OPERATIONAL 5 // Device is brought into OPERATIONAL mode - -/////////////////////////////////////////////////////////////////////////////// -// Constructor // -/////////////////////////////////////////////////////////////////////////////// +// A curious delay inserted in the original code [ms] +#define ENS160_BOOTING 10 + +// ENS160 registers for version V0 +#define ENS160_REG_PART_ID 0x00 // 2 byte register for part identification +#define ENS160_REG_OPMODE 0x10 // Operation mode register +#define ENS160_REG_CONFIG 0x11 // Pin configuration register +#define ENS160_REG_COMMAND 0x12 // Additional system commands +#define ENS160_REG_TEMP_IN 0x13 // Host ambient temperature information +#define ENS160_REG_RH_IN 0x15 // Host relative humidity information +#define ENS160_REG_DATA_STATUS 0x20 // Operating mode status readback +#define ENS160_REG_DATA_AQI 0x21 // Air quality according to index according to UBA +#define ENS160_REG_DATA_TVOC 0x22 // Equivalent TVOC concentartion (ppb) +#define ENS160_REG_DATA_ECO2 0x24 // Equivalent CO2 concentration (ppm) +#define ENS160_REG_DATA_BL 0x28 // Relative air quality index according to ScioSense +#define ENS160_REG_DATA_T 0x30 // Temperature used in calculations +#define ENS160_REG_DATA_RH 0x32 // Relative humidity used in calculations +#define ENS160_REG_DATA_MISR 0x38 //Data integrity field (optional) +#define ENS160_REG_GPR_WRITE_0 0x40 // General purpose write registers [8 bytes] +#define ENS160_REG_GPR_WRITE_1 ENS160_REG_GPR_WRITE_0 + 1 +#define ENS160_REG_GPR_WRITE_2 ENS160_REG_GPR_WRITE_0 + 2 +#define ENS160_REG_GPR_WRITE_3 ENS160_REG_GPR_WRITE_0 + 3 +#define ENS160_REG_GPR_WRITE_4 ENS160_REG_GPR_WRITE_0 + 4 +#define ENS160_REG_GPR_WRITE_5 ENS160_REG_GPR_WRITE_0 + 5 +#define ENS160_REG_GPR_WRITE_6 ENS160_REG_GPR_WRITE_0 + 6 +#define ENS160_REG_GPR_WRITE_7 ENS160_REG_GPR_WRITE_0 + 7 +#define ENS160_REG_GPR_READ_0 0x48 // General purpose read registers [8 bytes] +#define ENS160_REG_GPR_READ_4 ENS160_REG_GPR_READ_0 + 4 +#define ENS160_REG_GPR_READ_6 ENS160_REG_GPR_READ_0 + 6 +#define ENS160_REG_GPR_READ_7 ENS160_REG_GPR_READ_0 + 7 + +// ENS160_REG_PART_ID values for Chip ID +#define ENS160_PARTID 0x0160 // ENS160 +#define ENS161_PARTID 0x0161 // ENS161 + +//ENS160 COMMAND register values +#define ENS160_COMMAND_NOP 0x00 // NOP, No operation +#define ENS160_COMMAND_CLRGPR 0xCC // CLRGRP, Clears GPR Read Registers +#define ENS160_COMMAND_GET_APPVER 0x0E // GET_APPVER, Get firmware version +#define ENS160_COMMAND_SETTH 0x02 // Not specified in datasheet +#define ENS160_COMMAND_SETSEQ 0xC2 // Not specified in datasheet + +// ENS160 OPMODE register values +#define ENS160_OPMODE_RESET 0xF0 // RESET +#define ENS160_OPMODE_DEEP_SLEEP 0x00 // DEEPSLEEP +#define ENS160_OPMODE_IDLE 0x01 // IDLE +#define ENS160_OPMODE_STD 0x02 // STANDARD +#define ENS160_OPMODE_LP 0x03 // LOW POWER (ENS161 only) +#define ENS160_OPMODE_ULP 0x04 // ULTRA LOW POWER (ENS161 only) +#define ENS160_OPMODE_CUSTOM 0xC0 // Not specified in datasheet + +// ENS160 undefined bitfields? +#define ENS160_BL_CMD_START 0x02 +#define ENS160_BL_CMD_ERASE_APP 0x04 +#define ENS160_BL_CMD_ERASE_BLINE 0x06 +#define ENS160_BL_CMD_WRITE 0x08 +#define ENS160_BL_CMD_VERIFY 0x0A +#define ENS160_BL_CMD_GET_BLVER 0x0C +#define ENS160_BL_CMD_GET_APPVER 0x0E +#define ENS160_BL_CMD_EXITBL 0x12 + +// ENS160 undefined bitfields? +#define ENS160_SEQ_ACK_NOTCOMPLETE 0x80 +#define ENS160_SEQ_ACK_COMPLETE 0xC0 + +#define IS_ENS160_SEQ_ACK_NOT_COMPLETE(x) (ENS160_SEQ_ACK_NOTCOMPLETE == (ENS160_SEQ_ACK_NOTCOMPLETE & (x))) +#define IS_ENS160_SEQ_ACK_COMPLETE(x) (ENS160_SEQ_ACK_COMPLETE == (ENS160_SEQ_ACK_COMPLETE & (x))) + +// ENS160 STATUS bitfields +#define ENS160_STATUS_STATAS 0x80 // STATAS: Indicates that an OPMODE is rumming +#define ENS160_STATUS_STATER 0x40 // STATER: High indicated that an error is detected +#define ENS160_STATUS_VALIDITY 0x0C // VALIDITY FLAG +#define ENS160_STATUS_NEWDAT 0x02 // NEWDAT: 1= New data in data registers available +#define ENS160_STATUS_NEWGPR 0x01 // NEWGRP: 1= New data in GRP_READ registers available + +// Checkers for bitfields in STATUS register +#define IS_NEWDAT(x) (ENS160_STATUS_NEWDAT == (ENS160_STATUS_NEWDAT & (x))) +#define IS_NEWGPR(x) (ENS160_STATUS_NEWGPR == (ENS160_STATUS_NEWGPR & (x))) +#define IS_NEW_DATA_AVAILABLE(x) (0 != ((ENS160_STATUS_NEWDAT | ENS160_STATUS_NEWGPR ) & (x))) +#define GET_STATUS_VALIDITY(x) (((x) & ENS160_STATUS_VALIDITY) >> 2) + +// TODO: add comment on this +#define CONVERT_RS_RAW2OHMS_I(x) (1 << ((x) >> 11)) +#define CONVERT_RS_RAW2OHMS_F(x) (pow (2, (float)(x) / 2048)) + +// Form IDs used on the device setup page. Should be a short unique string. +#define P164_GUID_TEMP_T "f08" +#define P164_GUID_TEMP_V "f09" +#define P164_GUID_HUM_T "f10" +#define P164_GUID_HUM_V "f11" + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Constructor // +/////////////////////////////////////////////////////////////////////////////////////////////////// P164_data_struct::P164_data_struct(struct EventStruct *event) : i2cAddress(P164_I2C_ADDR) { - Serial.println(F("ENS160: Constructor")); - Serial.print("ENS160: I2C address ="); - Serial.println(i2cAddress, HEX); + #ifdef P164_ENS160_DEBUG + Serial.println(F("ENS160: Constructor")); + Serial.print("ENS160: I2C address ="); + Serial.println(i2cAddress, HEX); + #endif } -/////////////////////////////////////////////////////////////////////////////// -// Initialization of the connected device // -/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Initialization of the connected device // +/////////////////////////////////////////////////////////////////////////////////////////////////// bool P164_data_struct::begin() { if (!start(i2cAddress)) { - Serial.println(F("ENS160: P164_data_struct::begin() ***FAILED***")); + Serial.println(F("P164: device initialization ***FAILED***")); return false; } setMode(ENS160_OPMODE_STD); initialized = true; - Serial.println(F("ENS160: P164_data_struct::begin() success")); + #ifdef P164_ENS160_DEBUG + Serial.println(F("P164: begin(): success")); + #endif return true; } -/////////////////////////////////////////////////////////////////////////////// -// Fetch the processed device values as stored in the software object // -/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Fetch the processed device values as stored in the software object // +/////////////////////////////////////////////////////////////////////////////////////////////////// bool P164_data_struct::read(float& tvoc, float& eco2) { - if (measure()) { - return false; - } - - tvoc = getTVOC(); - eco2 = geteCO2(); + bool success = measure(); + tvoc = (float)_data_tvoc; + eco2 = (float)_data_eco2; + return success; +} - return true; +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Fetch the processed device values as stored in the software object using compensation // +/////////////////////////////////////////////////////////////////////////////////////////////////// +bool P164_data_struct::read(float& tvoc, float& eco2, float temp, float hum) +{ + this->set_envdata(temp, hum); // Write new compensation temp & hum to device + bool success = measure(); // Read emasurement values from device + tvoc = (float)_data_tvoc; // Latest acquired TVOC value + eco2 = (float)_data_eco2; // Latest aquired eCO2 value + return success; } -/////////////////////////////////////////////////////////////////////////////// -// Implementation of plugin PLUGIN_WEBFORM_LOAD call // -// Note: this is not a class function, only data from event is available // -/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Implementation of plugin PLUGIN_WEBFORM_LOAD call // +// Note: this is not a class function, only data from event is available // +/////////////////////////////////////////////////////////////////////////////////////////////////// bool P164_data_struct::webformLoad(struct EventStruct *event) { bool found = false; // A chip has responded at the I2C address + bool compensate = true; // TODO: make selectable uint8 reg0, reg1; uint16_t chipID = 0; @@ -80,48 +191,99 @@ bool P164_data_struct::webformLoad(struct EventStruct *event) addHtmlInt(chipID); } + addFormNote(F("Both Temperature and Humidity task & values are needed to enable compensation")); + // temperature + addRowLabel(F("Temperature Task")); + addTaskSelect(F(P164_GUID_TEMP_T), P164_PCONFIG_TEMP_TASK); + if (validTaskIndex(P164_PCONFIG_TEMP_TASK)) + { + LoadTaskSettings(P164_PCONFIG_TEMP_TASK); // we need to load the values from another task for selection! + addRowLabel(F("Temperature Value")); + addTaskValueSelect(F(P164_GUID_TEMP_V), P164_PCONFIG_TEMP_VAL, P164_PCONFIG_TEMP_TASK); + } + // humidity + addRowLabel(F("Humidity Task")); + addTaskSelect(F(P164_GUID_HUM_T), P164_PCONFIG_HUM_TASK); + if (validTaskIndex(P164_PCONFIG_HUM_TASK)) + { + LoadTaskSettings(P164_PCONFIG_HUM_TASK); // we need to load the values from another task for selection! + addRowLabel(F("Humidity Value")); + addTaskValueSelect(F(P164_GUID_HUM_V), P164_PCONFIG_HUM_VAL, P164_PCONFIG_HUM_TASK); + } + LoadTaskSettings(event->TaskIndex); // we need to restore our original taskvalues! + return true; } -/////////////////////////////////////////////////////////////////////////////// -/// Implementation of plugin PLUGIN_WEBFORM_SAVE call // -// Note: this is not a class function, only data from event is available // -/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////// +/// Implementation of plugin PLUGIN_WEBFORM_SAVE call // +// Note: this is not a class function, only data from event is available // +/////////////////////////////////////////////////////////////////////////////////////////////////// bool P164_data_struct::webformSave(struct EventStruct *event) { P164_I2C_ADDR = getFormItemInt(F("i2c_addr")); + + P164_PCONFIG_TEMP_TASK = getFormItemInt(F(P164_GUID_TEMP_T)); + P164_PCONFIG_TEMP_VAL = getFormItemInt(F(P164_GUID_TEMP_V)); + P164_PCONFIG_HUM_TASK = getFormItemInt(F(P164_GUID_HUM_T)); + P164_PCONFIG_HUM_VAL = getFormItemInt(F(P164_GUID_HUM_V)); return true; } -/////////////////////////////////////////////////////////////////////////////// -// Implementation of plugin PLUGIN_TEN_PER_SECOND call // -/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Implementation of plugin PLUGIN_TEN_PER_SECOND call // +/////////////////////////////////////////////////////////////////////////////////////////////////// bool P164_data_struct::tenPerSecond(struct EventStruct *event) { return evaluateState(); } -/////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////// -// **** The sensor handling code **** // -// This is based upon XXXXX code on github // -// The code is heavily adapted to fit the ESPEasy structures // -/////////////////////////////////////////////////////////////////////////////// - -/* - For documentation see https://www.sciosense.com/wp-content/uploads/documents/SC-001224-DS-9-ENS160-Datasheet.pdf - based on application note "ENS160 Software Integration.pdf" rev 0.01 - original code from Github TODO: - */ - -/////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////// - -void printState(int state); +/////////////////////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////// +// **** The sensor handling code **** // +// This is based upon Sciosense code on github // +// The code is adapted to fit the ESPEasy structures // +/////////////////////////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Helper function to display the current state in readable format // +/////////////////////////////////////////////////////////////////////////////////////////////////// +#ifdef P164_ENS160_DEBUG +void printState(int state) { + #ifdef P164_ENS160_DEBUG -/////////////////////////////////////////////////////// -// Evaluate the statemachine and determine next step // -/////////////////////////////////////////////////////// + switch (state) { + case ENS160_STATE_INITIAL: Serial.print(F("initial")); break; + case ENS160_STATE_ERROR: Serial.print(F("error")); break; + case ENS160_STATE_RESETTING: Serial.print(F("resetting")); break; + case ENS160_STATE_IDLE: Serial.print(F("idle")); break; + case ENS160_STATE_DEEPSLEEP: Serial.print(F("deepsleep")); break; + case ENS160_STATE_OPERATIONAL: Serial.print(F("operational")); break; + default: Serial.print(F("***ERROR***")); break; + } + #endif // ifdef P164_ENS160_DEBUG +} +#endif + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Evaluate the plugin statemachine and determine next step // +// To prevent using delay() to wait for the device responses a statemachine is introduced // +// Main states follow the device states according datasheet: // +// + IDLE Enabled, waiting for commands // +// + DEEP SLEEP Low power standby // +// + OPERATIONAL Active gas sensing // +// Added states to administrate plugin software status: // +// + INITIAL Class is constructed, waiting for begin() // +// + ERROR Communication with the device failed or other fatal error conditions // +// + RESETTING Waiting for device reset to be finished // +// Note that the ENS161 device has various gas sensing operation modes which all map to the // +// same OPERATIONAL state. These are combined in the same OPMODE register in the device // +// - STANDARD // +// - LOW POWER // +// - ULTRA LOW POWER // +// The same OPMODE register is also used to reset the device. // +// Thus OPMODE register shall not be confused with this software state // +/////////////////////////////////////////////////////////////////////////////////////////////////// bool P164_data_struct::evaluateState() { bool success = true; @@ -131,12 +293,16 @@ bool P164_data_struct::evaluateState() case ENS160_STATE_INITIAL: // Waiting for external call to begin() this->_available = false; - success = true; break; case ENS160_STATE_ERROR: - // Stay here until external retry attempt + // Stay here until correct device ID is detected and status register can be read + // If there is a device connected then reset it and initizlize it this->_available = false; - success = true; + success = this->checkPartID() && this->getStatus(); + if (success){ + success = this->writeMode(ENS160_OPMODE_RESET); // Reset the device, takes some time + newState = ENS160_STATE_RESETTING; + } break; case ENS160_STATE_RESETTING: this->_available = false; @@ -206,17 +372,17 @@ bool P164_data_struct::evaluateState() if (newState != this->_state) { this->_state = newState; this->_lastChange = millis(); - #ifdef P164_ENS160_DEBUG - Serial.print(F("ENS16x: State transition:")); - printState(newState); - Serial.print(F("; opmode ")); - Serial.print(this->_opmode); - Serial.println(F(".")); - #endif // ifdef P164_ENS160_DEBUG + #ifdef P164_ENS160_DEBUG + Serial.print(F("P164: State transition:")); + printState(newState); + Serial.print(F("; opmode ")); + Serial.print(this->_opmode); + Serial.println(F(".")); + #endif // ifdef P164_ENS160_DEBUG } else { #ifdef P164_ENS160_DEBUG - // Serial.print(F("ENS16x: state ")); + // Serial.print(F("P164: state ")); // printState(newState); // Serial.print(F(" opmode ")); // Serial.println(this->_opmode); @@ -226,10 +392,22 @@ bool P164_data_struct::evaluateState() return success; } -/////////////////////////////////////////////////////////////////// -// Init I2C communication, resets ENS160 and checks its PART_ID. // -// Returns false on I2C problems or wrong PART_ID. // -/////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Helper function to enter a new state // +/////////////////////////////////////////////////////////////////////////////////////////////////// +void P164_data_struct::moveToState(int newState) +{ + this->_state = newState; // Enter the new state + this->_lastChange = millis(); // Mark time of transition + this->evaluateState(); // Check if we can already move on +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Init the device: // +// Setup optional hardware pins (not implemented yet) // +// Reset ENS16x // +// Returns false on encountered errors // +/////////////////////////////////////////////////////////////////////////////////////////////////// bool P164_data_struct::start(uint8_t slaveaddr) { uint8_t result = 0; @@ -241,9 +419,10 @@ bool P164_data_struct::start(uint8_t slaveaddr) this->_opmode = ENS160_OPMODE_STD; // Set IO pin levels + // TODO: Pin definitions are not available on the user interface yet if (this->_ADDR > 0) { - pinMode(this->_ADDR, OUTPUT); // ADDR is input pin for device - digitalWrite(this->_ADDR, LOW); // Set it identify ENS160_I2CADDR_0 + pinMode(this->_ADDR, OUTPUT); // ADDR is input pin for device + digitalWrite(this->_ADDR, LOW); // Set it identify ENS160_I2CADDR_0 } if (this->_nINT > 0) { @@ -251,25 +430,24 @@ bool P164_data_struct::start(uint8_t slaveaddr) } if (this->_nCS > 0) { - pinMode(this->_nCS, OUTPUT); // CS is input for the device - digitalWrite(this->_nCS, HIGH); // Must be HIGH for I2C operation + pinMode(this->_nCS, OUTPUT); // CS is input for the device + digitalWrite(this->_nCS, HIGH); // Must be HIGH for I2C operation } - result = this->writeMode(ENS160_OPMODE_RESET); - - #ifdef P164_ENS160_DEBUG - Serial.print(F("ENS16x: reset() result: ")); - Serial.println(result == 0 ? F("ok") : F("nok")); - #endif // ifdef P164_ENS160_DEBUG + result = this->writeMode(ENS160_OPMODE_RESET); // Reset the device, takes some time this->moveToState(ENS160_STATE_RESETTING); // Go to next state RESETTING - return true; + #ifdef P164_ENS160_DEBUG + Serial.print(F("P164: reset() result: ")); + Serial.println(result == 0 ? F("ok") : F("nok")); + #endif + return result; } -/////////////////////////////////////////////////////////////////////////////// -// Initialize definition of custom mode with steps // -// TODO: Undocumented feature, to be reworked for ESPEasy version // -/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Initialize definition of custom mode with steps // +// This feature is not documented in the datasheet, but code is provided by Sciosense // +/////////////////////////////////////////////////////////////////////////////////////////////////// bool P164_data_struct::initCustomMode(uint16_t stepNum) { uint8_t result; @@ -282,17 +460,14 @@ bool P164_data_struct::initCustomMode(uint16_t stepNum) { result = 1; } - // TODO check if delay is needed - // delay(ENS160_BOOTING); // Wait to boot after reset - return result == 0; } -/////////////////////////////////////////////////////////////////////////////// -// Add a step to custom measurement profile with definition of duration // -// enabled data acquisition and temperature for each hotplate // -// TODO: Undocumented feature, to be reworked for ESPEasy version // -/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Add a step to custom measurement profile with definition of duration // +// enabled data acquisition and temperature for each hotplate // +// This feature is not documented in the datasheet, but code is provided by Sciosense // +/////////////////////////////////////////////////////////////////////////////////////////////////// bool P164_data_struct::addCustomStep(uint16_t time, bool measureHP0, bool measureHP1, bool measureHP2, bool measureHP3, uint16_t tempHP0, uint16_t tempHP1, uint16_t tempHP2, uint16_t tempHP3) { @@ -310,11 +485,8 @@ bool P164_data_struct::addCustomStep(uint16_t time, bool measureHP0, bool measur temp = (uint8_t)(((time / 24) - 1) << 6); if (measureHP0) { temp = temp | 0x20; } - if (measureHP1) { temp = temp | 0x10; } - if (measureHP2) { temp = temp | 0x8; } - if (measureHP3) { temp = temp | 0x4; } P164_data_struct::write8(i2cAddress, ENS160_REG_GPR_WRITE_0, temp); @@ -344,74 +516,82 @@ bool P164_data_struct::addCustomStep(uint16_t time, bool measureHP0, bool measur if ((ENS160_SEQ_ACK_COMPLETE | this->_stepCount) != seq_ack) { this->_stepCount = this->_stepCount - 1; - return 0; + return false; } else { - return 1; + return true; } } -/////////////////////////////////////////////////////////////////////////// -// Perform prediction measurement and store result in internal variables // -// Return: true if data is fresh (first reading of new data) // -/////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Perform prediction measurement and store result in internal variables // +// Return: true if data is fresh (first reading of new data) // +/////////////////////////////////////////////////////////////////////////////////////////////////// bool P164_data_struct::measure() { uint8_t i2cbuf[8]; uint8_t status; bool newData = false; - #ifdef P164_ENS160_DEBUG - Serial.println(F("ENS16x: Start measurement")); - #endif // ifdef P164_ENS160_DEBUG + #ifdef P164_ENS160_DEBUG + Serial.println(F("P164: Start measurement")); + #endif // ifdef P164_ENS160_DEBUG if (this->_state == ENS160_STATE_OPERATIONAL) { // Check if new data is aquired - status = P164_data_struct::read8(i2cAddress, ENS160_REG_DATA_STATUS); - - // Read predictions - if (IS_NEWDAT(status)) { - newData = true; - P164_data_struct::read(i2cAddress, ENS160_REG_DATA_AQI, i2cbuf, 7); - _data_aqi = i2cbuf[0]; - _data_tvoc = i2cbuf[1] | ((uint16_t)i2cbuf[2] << 8); - _data_eco2 = i2cbuf[3] | ((uint16_t)i2cbuf[4] << 8); - - if (_revENS16x > 0) { - _data_aqi500 = ((uint16_t)i2cbuf[5]) | ((uint16_t)i2cbuf[6] << 8); - } - else { - _data_aqi500 = 0; + if (this->getStatus()) { + status = this->_statusReg; + + // Read predictions + if (IS_NEWDAT(status)) { + newData = true; + P164_data_struct::read(i2cAddress, ENS160_REG_DATA_AQI, i2cbuf, 7); + _data_aqi = i2cbuf[0]; + _data_tvoc = i2cbuf[1] | ((uint16_t)i2cbuf[2] << 8); + _data_eco2 = i2cbuf[3] | ((uint16_t)i2cbuf[4] << 8); + + if (_revENS16x > 0) { + _data_aqi500 = ((uint16_t)i2cbuf[5]) | ((uint16_t)i2cbuf[6] << 8); + } + else { + _data_aqi500 = 0; + } } } + else { + // Some issues with the device connectivity, move to error state + this->moveToState(ENS160_STATE_ERROR); + } } - #ifdef P164_ENS160_DEBUG - Serial.print(F("ENS16x: measure: aqi= ")); - Serial.print(_data_aqi); - Serial.print(F(" tvoc= ")); - Serial.print(_data_tvoc); - Serial.print(F(" eco2= ")); - Serial.print(_data_eco2); - Serial.println(F(".")); - #endif // ifdef P164_ENS160_DEBUG + #ifdef P164_ENS160_DEBUG + Serial.print(F("P164: measure: aqi= ")); + Serial.print(_data_aqi); + Serial.print(F(" tvoc= ")); + Serial.print(_data_tvoc); + Serial.print(F(" eco2= ")); + Serial.print(_data_eco2); + Serial.print(F(" newdata = ")); + Serial.println(newData); + #endif // ifdef P164_ENS160_DEBUG return newData; } -//////////////////////////////////////////////////////////////////// -// Perfrom raw measurement and store result in internal variables // -//////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Perfrom raw measurement and store result in internal variables // +/////////////////////////////////////////////////////////////////////////////////////////////////// bool P164_data_struct::measureRaw() { uint8_t i2cbuf[8]; uint8_t status; bool newData = false; // Set default status for early bail out - #ifdef P164_ENS160_DEBUG - Serial.println("ENS16x: Start measurement"); - #endif // ifdef P164_ENS160_DEBUG + #ifdef P164_ENS160_DEBUG + Serial.println("ENS16x: Start measurement"); + #endif // ifdef P164_ENS160_DEBUG if (this->_state == ENS160_STATE_OPERATIONAL) { - status = P164_data_struct::read8(i2cAddress, ENS160_REG_DATA_STATUS); + this->getStatus(); + status = this->_statusReg; if (IS_NEWGPR(status)) { newData = true; @@ -438,9 +618,9 @@ bool P164_data_struct::measureRaw() { return newData; } -/////////////////////////////////////////////////////////////////////////////// -// Writes t (degC) and h (%rh) to ENV_DATA. Returns false on I2C problems. // -/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Writes t (degC) and h (%rh) to ENV_DATA. Returns false on I2C problems. // +/////////////////////////////////////////////////////////////////////////////////////////////////// bool P164_data_struct::set_envdata(float t, float h) { uint16_t t_data = (uint16_t)((t + 273.15f) * 64.0f); uint16_t rh_data = (uint16_t)(h * 512.0f); @@ -448,9 +628,9 @@ bool P164_data_struct::set_envdata(float t, float h) { return this->set_envdata210(t_data, rh_data); } -/////////////////////////////////////////////////////////////////////////////// -// Writes t and h (in ENS210 format) to ENV_DATA. Returns false on I2C problems. -/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Writes t and h (in ENS210 format) to ENV_DATA. Returns false on I2C problems. // +/////////////////////////////////////////////////////////////////////////////////////////////////// bool P164_data_struct::set_envdata210(uint16_t t, uint16_t h) { uint8_t trh_in[4]; // Buffer for I2C registers TEMP_IN + RH_IN @@ -467,20 +647,10 @@ bool P164_data_struct::set_envdata210(uint16_t t, uint16_t h) { return result; } -/////////////////////////////////////////////////////////////////////////////// -// Helper function to enter a new state // -/////////////////////////////////////////////////////////////////////////////// -void P164_data_struct::moveToState(int newState) -{ - this->_state = newState; // Enter the new state - this->_lastChange = millis(); // Mark time of transition - this->evaluateState(); // Check if we can already move on -} - -/////////////////////////////////////////////////////////////////////////////// -// Read firmware revision // -// Precondition: Device MODE is IDLE // -/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Read firmware revision // +// Precondition: Device MODE is IDLE // +/////////////////////////////////////////////////////////////////////////////////////////////////// bool P164_data_struct::getFirmware() { uint8_t i2cbuf[3]; uint8_t result; @@ -502,22 +672,24 @@ bool P164_data_struct::getFirmware() { this->_fw_ver_build = 0; } - #ifdef P164_ENS160_DEBUG - Serial.print(F("ENS16x: getFirmware() result: ")); - Serial.print(result == 0 ? "ok," : "nok,"); - Serial.print(this->_fw_ver_major); - Serial.print(F(",")); - Serial.println(this->_fw_ver_minor); - Serial.print(F(",")); - Serial.println(this->_fw_ver_build); - #endif // ifdef P164_ENS160_DEBUG + #ifdef P164_ENS160_DEBUG + Serial.print(F("P164: getFirmware() result: ")); + Serial.print(result == 0 ? "ok," : "nok, major= "); + Serial.print(this->_fw_ver_major); + Serial.print(F(", minor=")); + Serial.print(this->_fw_ver_minor); + Serial.print(F(", build=")); + Serial.println(this->_fw_ver_build); + #endif // ifdef P164_ENS160_DEBUG return result == 0; } -////////////////////////////////// -// Set operation mode of sensor // -////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Set operation mode of sensor // +// Note: This function is to set the operation mode to the plugin software structure only // +// The statemachine shall handle the actual programming of the OPMODE register // +/////////////////////////////////////////////////////////////////////////////////////////////////// bool P164_data_struct::setMode(uint8_t mode) { bool result = false; @@ -527,34 +699,34 @@ bool P164_data_struct::setMode(uint8_t mode) { result = true; } - #ifdef P164_ENS160_DEBUG - Serial.print(F("ENS16x: setMode(")); - Serial.print(mode); - Serial.println(F(")")); - #endif // ifdef P164_ENS160_DEBUG + #ifdef P164_ENS160_DEBUG + Serial.print(F("P164: setMode(")); + Serial.print(mode); + Serial.println(F(")")); + #endif // ifdef P164_ENS160_DEBUG return result; } -/////////////////////////////////////////////////////////////////////////////// -// Write to opmode register of the device // -/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Write to opmode register of the device // +/////////////////////////////////////////////////////////////////////////////////////////////////// bool P164_data_struct::writeMode(uint8_t mode) { uint8_t result; result = P164_data_struct::write8(i2cAddress, ENS160_REG_OPMODE, mode); #ifdef P164_ENS160_DEBUG - Serial.print(F("ENS16x: writeMode() activate result: ")); - Serial.println(result == 0 ? F("ok") : F("nok")); + Serial.print(F("P164: writeMode() activate result: ")); + Serial.println(result == 0 ? F("ok") : F("nok")); #endif // ifdef P164_ENS160_DEBUG return result == 0; } -/////////////////////////////////////////////////////////////////////////////// -// Read the part ID from ENS160 device and check for validity // -/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Read the part ID from ENS160 device and check for validity // +/////////////////////////////////////////////////////////////////////////////////////////////////// bool P164_data_struct::checkPartID(void) { uint8_t i2cbuf[2]; // Buffer for returned I2C data uint16_t part_id; // Resulting PartID @@ -576,71 +748,61 @@ bool P164_data_struct::checkPartID(void) { result = false; } - #ifdef P164_ENS160_DEBUG - Serial.print(F("ENS16x: checkPartID() result: ")); - - if (part_id == ENS160_PARTID) { Serial.println(F("ENS160 ok")); } - else if (part_id == ENS161_PARTID) { Serial.println(F("ENS161 ok")); } - else { Serial.println(F("nok")); } - #endif // ifdef P164_ENS160_DEBUG + #ifdef P164_ENS160_DEBUG + Serial.print(F("P164: checkPartID() result: ")); + if (part_id == ENS160_PARTID) { Serial.println(F("ENS160")); } + else if (part_id == ENS161_PARTID) { Serial.println(F("ENS161")); } + else { Serial.println(F("no valid part ID read")); } + #endif // ifdef P164_ENS160_DEBUG return result; } -/////////////////////////////////////////////////////////////////////////////// -// Clear any pending command in device // -/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Clear any pending command in device // +/////////////////////////////////////////////////////////////////////////////////////////////////// bool P164_data_struct::clearCommand(void) { - uint8_t status; - uint8_t result; + bool result = false; result = P164_data_struct::write8(i2cAddress, ENS160_REG_COMMAND, ENS160_COMMAND_NOP); result = P164_data_struct::write8(i2cAddress, ENS160_REG_COMMAND, ENS160_COMMAND_CLRGPR); - #ifdef P164_ENS160_DEBUG - Serial.print(F("ENS16x: clearCommand() result: ")); - Serial.println(result == 0 ? "ok" : "nok"); - #endif // ifdef P164_ENS160_DEBUG + #ifdef P164_ENS160_DEBUG + Serial.print(F("P164: clearCommand() result: ")); + Serial.println(result == 0 ? "ok" : "nok"); + #endif // ifdef P164_ENS160_DEBUG - return result == 0; + return result; } -/////////////////////////////////////////////////////////////////////////////// -// Read status register from device // -/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Read status register from device // +/////////////////////////////////////////////////////////////////////////////////////////////////// bool P164_data_struct::getStatus() { this->_statusReg = P164_data_struct::read8(i2cAddress, ENS160_REG_DATA_STATUS); - #ifdef P164_ENS160_DEBUG - Serial.print(F("ENS16x: Status register: 0x")); - Serial.println(this->_statusReg, HEX); - #endif // ifdef P164_ENS160_DEBUG + #ifdef P164_ENS160_DEBUG + Serial.print(F("P164: Status register: 0x")); + Serial.print(this->_statusReg, HEX); + Serial.print(F(" VALIDITY: ")); + Serial.print(GET_STATUS_VALIDITY(this->_statusReg) ); + Serial.print(F(" STATAS: ")); + Serial.print((this->_statusReg & ENS160_STATUS_STATAS) == ENS160_STATUS_STATAS); + Serial.print(F(" STATER: ")); + Serial.print((this->_statusReg & ENS160_STATUS_STATER) == ENS160_STATUS_STATER); + Serial.print(F(" NEWDAT: ")); + Serial.print((this->_statusReg & ENS160_STATUS_NEWDAT) == ENS160_STATUS_NEWDAT); + Serial.print(F(" NEWGRP: ")); + Serial.println((this->_statusReg & ENS160_STATUS_NEWGPR) == ENS160_STATUS_NEWGPR); + #endif // ifdef P164_ENS160_DEBUG return true; } -/////////////////////////////////////////////////////////////////////////////// -// Helper function to display the current state in readable format // -/////////////////////////////////////////////////////////////////////////////// -void printState(int state) { - #ifdef P164_ENS160_DEBUG - - switch (state) { - case ENS160_STATE_INITIAL: Serial.print(F("initial")); break; - case ENS160_STATE_ERROR: Serial.print(F("error")); break; - case ENS160_STATE_RESETTING: Serial.print(F("resetting")); break; - case ENS160_STATE_IDLE: Serial.print(F("idle")); break; - case ENS160_STATE_DEEPSLEEP: Serial.print(F("deepsleep")); break; - case ENS160_STATE_OPERATIONAL: Serial.print(F("operational")); break; - default: Serial.print(F("***ERROR***")); break; - } - #endif // ifdef P164_ENS160_DEBUG -} - -/////////////////////////////////////////////////////////////////////////////// -// TODO: Refactor I2C to use the ESPEasy standard // -/////////////////////////////////////////////////////////////////////////////// - -/**************************************************************************/ +/////////////////////////////////////////////////////////////////////////////////////////////////// +// I2C functionality copied from Sciosense. TODO: Refactor I2C to use the ESPEasy standard // +/////////////////////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////// uint8_t P164_data_struct::read8(uint8_t addr, byte reg) { uint8_t ret; @@ -649,12 +811,14 @@ uint8_t P164_data_struct::read8(uint8_t addr, byte reg) { return ret; } +/////////////////////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////// bool P164_data_struct::read(uint8_t addr, uint8_t reg, uint8_t *buf, uint8_t num) { uint8_t pos = 0; bool result = true; #ifdef P164_ENS160_DEBUG - Serial.print(F("ENS16x: I2C read address: 0x")); + Serial.print(F("P164: I2C read address: 0x")); Serial.print(addr, HEX); Serial.print(F(", register: 0x")); Serial.print(reg, HEX); @@ -686,17 +850,19 @@ bool P164_data_struct::read(uint8_t addr, uint8_t reg, uint8_t *buf, uint8_t num return result; } -/**************************************************************************/ - +/////////////////////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////// bool P164_data_struct::write8(uint8_t addr, byte reg, byte value) { return P164_data_struct::write(addr, reg, &value, 1); } +/////////////////////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////// bool P164_data_struct::write(uint8_t addr, uint8_t reg, uint8_t *buf, uint8_t num) { uint8_t result; #ifdef P164_ENS160_DEBUG - Serial.print(F("ENS16x: I2C write address: 0x")); + Serial.print(F("P164: I2C write address: 0x")); Serial.print(addr, HEX); Serial.print(F(", register: 0x")); Serial.print(reg, HEX); @@ -716,6 +882,4 @@ bool P164_data_struct::write(uint8_t addr, uint8_t reg, uint8_t *buf, uint8_t nu return result; } -/**************************************************************************/ - #endif // ifdef USES_P164 diff --git a/src/src/PluginStructs/P164_data_struct.h b/src/src/PluginStructs/P164_data_struct.h index bcce4452b0..b58ff2aeb0 100644 --- a/src/src/PluginStructs/P164_data_struct.h +++ b/src/src/PluginStructs/P164_data_struct.h @@ -1,12 +1,28 @@ +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Plugin data structure for P164 "GASES - ENS16x (TVOC, eCO2)" +// Plugin for ENS160 & ENS161 TVOC and eCO2 sensor with I2C interface from ScioSense +// Based upon: https://github.com/sciosense/ENS160_driver +// For documentation see +// https://www.sciosense.com/wp-content/uploads/documents/SC-001224-DS-9-ENS160-Datasheet.pdf +// +// 2023 By flashmark +/////////////////////////////////////////////////////////////////////////////////////////////////// + #ifndef PLUGINSTRUCTS_P164_DATA_STRUCT_H #define PLUGINSTRUCTS_P164_DATA_STRUCT_H #include "../../_Plugin_Helper.h" #ifdef USES_P164 -#include "../Pluginstructs/P164_ENS160.h" +#define P164_I2C_ADDR PCONFIG(0) +#define P164_PCONFIG_TEMP_TASK PCONFIG(1) +#define P164_PCONFIG_TEMP_VAL PCONFIG(2) +#define P164_PCONFIG_HUM_TASK PCONFIG(3) +#define P164_PCONFIG_HUM_VAL PCONFIG(4) -# define P164_I2C_ADDR PCONFIG(0) +// 7-bit I2C slave address of the ENS160 +#define ENS160_I2CADDR_0 0x52 //ADDR low +#define ENS160_I2CADDR_1 0x53 //ADDR high struct P164_data_struct : public PluginTaskData_base { public: @@ -17,6 +33,7 @@ struct P164_data_struct : public PluginTaskData_base { bool begin(); bool read(float& tvoc, float& eco2); + bool read(float& tvoc, float& eco2, float temp, float hum); static bool webformLoad(struct EventStruct *event); static bool webformSave(struct EventStruct *event); bool tenPerSecond(struct EventStruct *event); @@ -26,7 +43,6 @@ struct P164_data_struct : public PluginTaskData_base { bool initialized = false; bool evaluateState(); // Evaluate state machine for next action. Should be called regularly. - bool start(uint8_t slaveaddr); // Init I2C communication, resets ENS160 and checks its PART_ID. Returns false on I2C problems or wrong PART_ID. bool available() { return this->_available; } // Report availability of sensor uint8_t revENS16x() { return this->_revENS16x; } // Report version of sensor (0: ENS160, 1: ENS161) @@ -64,7 +80,7 @@ struct P164_data_struct : public PluginTaskData_base { int _state = 0; // General state of the software ulong _lastChange = 0u; // Timestamp of last state transition - uint8_t _opmode = 0; // ENS160 Mode + uint8_t _opmode = 0; // Selected ENS160 Mode (as requested by higher level software) bool checkPartID(); // Reads the part ID and confirms valid sensor bool clearCommand(); // Initialize idle mode and confirms @@ -101,9 +117,6 @@ struct P164_data_struct : public PluginTaskData_base { //Isotherm, HP0 252°C / HP1 350°C / HP2 250°C / HP3 324°C / measure every 1008ms uint8_t _seq_steps[1][8] = { { 0x7C, 0x0A, 0x7E, 0xAF, 0xAF, 0xA2, 0x00, 0x80 }, }; -/****************************************************************************/ -/* General functions */ -/****************************************************************************/ void moveToState(int newState); static uint8_t read8(uint8_t addr, byte reg); From 875097fcbaf9599335e370d01f57ff09315dc19c Mon Sep 17 00:00:00 2001 From: flashmark <15078748+flashmark@users.noreply.github.com> Date: Mon, 11 Dec 2023 21:55:29 +0100 Subject: [PATCH 03/10] Added recovery from communication failures --- src/src/PluginStructs/P164_data_struct.cpp | 246 +++++++++++---------- src/src/PluginStructs/P164_data_struct.h | 85 +++---- 2 files changed, 171 insertions(+), 160 deletions(-) diff --git a/src/src/PluginStructs/P164_data_struct.cpp b/src/src/PluginStructs/P164_data_struct.cpp index 74ed284d01..d93db8dca1 100644 --- a/src/src/PluginStructs/P164_data_struct.cpp +++ b/src/src/PluginStructs/P164_data_struct.cpp @@ -12,16 +12,6 @@ #ifdef USES_P164 -#define P164_ENS160_DEBUG // Enable debugging using teh serial port - -// Use a state machine to avoid blocking the CPU while waiting for the response -#define ENS160_STATE_INITIAL 0 // Device is in an unknown state, typically after reset -#define ENS160_STATE_ERROR 1 // Device is in an error state -#define ENS160_STATE_RESETTING 2 // Waiting for response after reset -#define ENS160_STATE_IDLE 3 // Device is brought into IDLE mode -#define ENS160_STATE_DEEPSLEEP 4 // Device is brought into DEEPSLEEP mode -#define ENS160_STATE_OPERATIONAL 5 // Device is brought into OPERATIONAL mode - // A curious delay inserted in the original code [ms] #define ENS160_BOOTING 10 @@ -113,6 +103,7 @@ #define P164_GUID_HUM_T "f10" #define P164_GUID_HUM_V "f11" + /////////////////////////////////////////////////////////////////////////////////////////////////// // Constructor // /////////////////////////////////////////////////////////////////////////////////////////////////// @@ -136,7 +127,6 @@ bool P164_data_struct::begin() return false; } setMode(ENS160_OPMODE_STD); - initialized = true; #ifdef P164_ENS160_DEBUG Serial.println(F("P164: begin(): success")); #endif @@ -173,8 +163,6 @@ bool P164_data_struct::read(float& tvoc, float& eco2, float temp, float hum) bool P164_data_struct::webformLoad(struct EventStruct *event) { bool found = false; // A chip has responded at the I2C address - bool compensate = true; // TODO: make selectable - uint8 reg0, reg1; uint16_t chipID = 0; addRowLabel(F("Detected Sensor Type")); @@ -253,12 +241,12 @@ void printState(int state) { #ifdef P164_ENS160_DEBUG switch (state) { - case ENS160_STATE_INITIAL: Serial.print(F("initial")); break; - case ENS160_STATE_ERROR: Serial.print(F("error")); break; - case ENS160_STATE_RESETTING: Serial.print(F("resetting")); break; - case ENS160_STATE_IDLE: Serial.print(F("idle")); break; - case ENS160_STATE_DEEPSLEEP: Serial.print(F("deepsleep")); break; - case ENS160_STATE_OPERATIONAL: Serial.print(F("operational")); break; + case P164_STATE_INITIAL: Serial.print(F("initial")); break; + case P164_STATE_ERROR: Serial.print(F("error")); break; + case P164_STATE_RESETTING: Serial.print(F("resetting")); break; + case P164_STATE_IDLE: Serial.print(F("idle")); break; + case P164_STATE_DEEPSLEEP: Serial.print(F("deepsleep")); break; + case P164_STATE_OPERATIONAL: Serial.print(F("operational")); break; default: Serial.print(F("***ERROR***")); break; } #endif // ifdef P164_ENS160_DEBUG @@ -286,25 +274,25 @@ void printState(int state) { /////////////////////////////////////////////////////////////////////////////////////////////////// bool P164_data_struct::evaluateState() { - bool success = true; - int newState = this->_state; // Determine next state + bool success = true; // State transition went without problems with device + P164_state newState = this->_state; // Determine next state, start with current state switch (this->_state) { - case ENS160_STATE_INITIAL: + case P164_STATE_INITIAL: // Waiting for external call to begin() this->_available = false; break; - case ENS160_STATE_ERROR: + case P164_STATE_ERROR: // Stay here until correct device ID is detected and status register can be read - // If there is a device connected then reset it and initizlize it + // If there is a proper device connected then reset it and initialize it this->_available = false; success = this->checkPartID() && this->getStatus(); if (success){ success = this->writeMode(ENS160_OPMODE_RESET); // Reset the device, takes some time - newState = ENS160_STATE_RESETTING; + newState = P164_STATE_RESETTING; } break; - case ENS160_STATE_RESETTING: + case P164_STATE_RESETTING: this->_available = false; // Once device has rebooted read some stuff from it and move to idle @@ -314,58 +302,58 @@ bool P164_data_struct::evaluateState() this->clearCommand(); this->getFirmware(); this->getStatus(); - newState = ENS160_STATE_IDLE; + newState = P164_STATE_IDLE; } break; - case ENS160_STATE_IDLE: + case P164_STATE_IDLE: // Set device into desired operation mode as requested through _opmode this->_available = true; switch (this->_opmode) { case ENS160_OPMODE_STD: this->writeMode(ENS160_OPMODE_STD); - newState = ENS160_STATE_OPERATIONAL; + newState = P164_STATE_OPERATIONAL; break; case ENS160_OPMODE_LP: this->writeMode(ENS160_OPMODE_LP); - newState = ENS160_STATE_OPERATIONAL; + newState = P164_STATE_OPERATIONAL; break; case ENS160_OPMODE_ULP: this->writeMode(ENS160_OPMODE_ULP); - newState = ENS160_STATE_OPERATIONAL; + newState = P164_STATE_OPERATIONAL; break; case ENS160_OPMODE_RESET: this->writeMode(ENS160_OPMODE_RESET); this->_opmode = ENS160_OPMODE_IDLE; // Prevent reset loop - newState = ENS160_STATE_RESETTING; + newState = P164_STATE_RESETTING; break; } break; - case ENS160_STATE_DEEPSLEEP: + case P164_STATE_DEEPSLEEP: // Device is put to DEEPSLEEP mode. If requested move to another mode. But alsways through IDLE this->_available = true; if (this->_opmode != ENS160_OPMODE_DEEP_SLEEP) { this->writeMode(ENS160_OPMODE_IDLE); // Move through Idle state - newState = ENS160_STATE_IDLE; + newState = P164_STATE_IDLE; } break; - case ENS160_STATE_OPERATIONAL: + case P164_STATE_OPERATIONAL: // Device is in one of the operational modes this->_available = true; switch (this->_opmode) { case ENS160_OPMODE_DEEP_SLEEP: - case ENS160_STATE_IDLE: + case P164_STATE_IDLE: case ENS160_OPMODE_RESET: this->writeMode(ENS160_OPMODE_IDLE); // Move through Idle state - newState = ENS160_STATE_IDLE; + newState = P164_STATE_IDLE; break; } break; default: // Unplanned state, force into error state - newState = ENS160_STATE_ERROR; + newState = P164_STATE_ERROR; break; } @@ -373,20 +361,23 @@ bool P164_data_struct::evaluateState() this->_state = newState; this->_lastChange = millis(); #ifdef P164_ENS160_DEBUG - Serial.print(F("P164: State transition:")); + Serial.print(F("P164: State transition->")); printState(newState); - Serial.print(F("; opmode ")); + Serial.print(F("; opmode= ")); Serial.print(this->_opmode); Serial.println(F(".")); #endif // ifdef P164_ENS160_DEBUG } else { - #ifdef P164_ENS160_DEBUG - // Serial.print(F("P164: state ")); - // printState(newState); - // Serial.print(F(" opmode ")); - // Serial.println(this->_opmode); - #endif + #ifdef P164_ENS160_DEBUG +// if (millis() > (this->_dbgtm + 1000)) { +// this->_dbgtm = millis(); +// Serial.print(F("P164: state ")); +// printState(newState); +// Serial.print(F(" opmode ")); +// Serial.println(this->_opmode); +// } + #endif } return success; @@ -395,7 +386,7 @@ bool P164_data_struct::evaluateState() /////////////////////////////////////////////////////////////////////////////////////////////////// // Helper function to enter a new state // /////////////////////////////////////////////////////////////////////////////////////////////////// -void P164_data_struct::moveToState(int newState) +void P164_data_struct::moveToState(P164_state newState) { this->_state = newState; // Enter the new state this->_lastChange = millis(); // Mark time of transition @@ -413,29 +404,19 @@ bool P164_data_struct::start(uint8_t slaveaddr) uint8_t result = 0; // Initialize internal bookkeeping; - this->_state = ENS160_STATE_INITIAL; // Assume nothing, start clean + this->_state = P164_STATE_INITIAL; // Assume nothing, start clean this->_lastChange = millis(); // Bookmark last state change as now this->_available = false; this->_opmode = ENS160_OPMODE_STD; // Set IO pin levels - // TODO: Pin definitions are not available on the user interface yet - if (this->_ADDR > 0) { - pinMode(this->_ADDR, OUTPUT); // ADDR is input pin for device - digitalWrite(this->_ADDR, LOW); // Set it identify ENS160_I2CADDR_0 - } - + // TODO: It is doubtable we will use the INT pin in future if (this->_nINT > 0) { pinMode(this->_nINT, INPUT_PULLUP); // INT is open drain output pin for the device } - if (this->_nCS > 0) { - pinMode(this->_nCS, OUTPUT); // CS is input for the device - digitalWrite(this->_nCS, HIGH); // Must be HIGH for I2C operation - } - result = this->writeMode(ENS160_OPMODE_RESET); // Reset the device, takes some time - this->moveToState(ENS160_STATE_RESETTING); // Go to next state RESETTING + this->moveToState(P164_STATE_RESETTING); // Go to next state RESETTING #ifdef P164_ENS160_DEBUG Serial.print(F("P164: reset() result: ")); @@ -444,6 +425,66 @@ bool P164_data_struct::start(uint8_t slaveaddr) return result; } +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Perform prediction measurement and store result in internal variables // +// Return: true if data is fresh (first reading of new data) // +/////////////////////////////////////////////////////////////////////////////////////////////////// +bool P164_data_struct::measure() { + uint8_t i2cbuf[8]; + uint8_t status; + bool newData = false; + + #ifdef P164_ENS160_DEBUG + Serial.println(F("P164: Start measurement")); + #endif // ifdef P164_ENS160_DEBUG + + if (this->_state == P164_STATE_OPERATIONAL) { + // Check if new data is aquired + if (this->getStatus()) { + status = this->_statusReg; + + // Read predictions + if (IS_NEWDAT(status)) { + newData = true; + P164_data_struct::read(i2cAddress, ENS160_REG_DATA_AQI, i2cbuf, 7); + _data_aqi = i2cbuf[0]; + _data_tvoc = i2cbuf[1] | ((uint16_t)i2cbuf[2] << 8); + _data_eco2 = i2cbuf[3] | ((uint16_t)i2cbuf[4] << 8); + + if (_revENS16x > 0) { + _data_aqi500 = ((uint16_t)i2cbuf[5]) | ((uint16_t)i2cbuf[6] << 8); + } + else { + _data_aqi500 = 0; + } + } + } + else { + // Some issues with the device connectivity, move to error state + this->moveToState(P164_STATE_ERROR); + } + } + + #ifdef P164_ENS160_DEBUG + Serial.print(F("P164: measure: aqi= ")); + Serial.print(_data_aqi); + Serial.print(F(" tvoc= ")); + Serial.print(_data_tvoc); + Serial.print(F(" eco2= ")); + Serial.print(_data_eco2); + Serial.print(F(" newdata = ")); + Serial.println(newData); + #endif // ifdef P164_ENS160_DEBUG + + return newData; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Following code is used to handle custom aquisition modes as defined by Sciosense Github code // +// Note that custom modes are not documented in the official datasheet // +/////////////////////////////////////////////////////////////////////////////////////////////////// +#ifdef P164_USE_CUSTOMMODE + /////////////////////////////////////////////////////////////////////////////////////////////////// // Initialize definition of custom mode with steps // // This feature is not documented in the datasheet, but code is provided by Sciosense // @@ -522,60 +563,6 @@ bool P164_data_struct::addCustomStep(uint16_t time, bool measureHP0, bool measur } } -/////////////////////////////////////////////////////////////////////////////////////////////////// -// Perform prediction measurement and store result in internal variables // -// Return: true if data is fresh (first reading of new data) // -/////////////////////////////////////////////////////////////////////////////////////////////////// -bool P164_data_struct::measure() { - uint8_t i2cbuf[8]; - uint8_t status; - bool newData = false; - - #ifdef P164_ENS160_DEBUG - Serial.println(F("P164: Start measurement")); - #endif // ifdef P164_ENS160_DEBUG - - if (this->_state == ENS160_STATE_OPERATIONAL) { - // Check if new data is aquired - if (this->getStatus()) { - status = this->_statusReg; - - // Read predictions - if (IS_NEWDAT(status)) { - newData = true; - P164_data_struct::read(i2cAddress, ENS160_REG_DATA_AQI, i2cbuf, 7); - _data_aqi = i2cbuf[0]; - _data_tvoc = i2cbuf[1] | ((uint16_t)i2cbuf[2] << 8); - _data_eco2 = i2cbuf[3] | ((uint16_t)i2cbuf[4] << 8); - - if (_revENS16x > 0) { - _data_aqi500 = ((uint16_t)i2cbuf[5]) | ((uint16_t)i2cbuf[6] << 8); - } - else { - _data_aqi500 = 0; - } - } - } - else { - // Some issues with the device connectivity, move to error state - this->moveToState(ENS160_STATE_ERROR); - } - } - - #ifdef P164_ENS160_DEBUG - Serial.print(F("P164: measure: aqi= ")); - Serial.print(_data_aqi); - Serial.print(F(" tvoc= ")); - Serial.print(_data_tvoc); - Serial.print(F(" eco2= ")); - Serial.print(_data_eco2); - Serial.print(F(" newdata = ")); - Serial.println(newData); - #endif // ifdef P164_ENS160_DEBUG - - return newData; -} - /////////////////////////////////////////////////////////////////////////////////////////////////// // Perfrom raw measurement and store result in internal variables // /////////////////////////////////////////////////////////////////////////////////////////////////// @@ -589,7 +576,7 @@ bool P164_data_struct::measureRaw() { Serial.println("ENS16x: Start measurement"); #endif // ifdef P164_ENS160_DEBUG - if (this->_state == ENS160_STATE_OPERATIONAL) { + if (this->_state == P164_STATE_OPERATIONAL) { this->getStatus(); status = this->_statusReg; @@ -617,6 +604,7 @@ bool P164_data_struct::measureRaw() { return newData; } +#endif // P164_USE_CUSTOMMODE /////////////////////////////////////////////////////////////////////////////////////////////////// // Writes t (degC) and h (%rh) to ENV_DATA. Returns false on I2C problems. // @@ -717,7 +705,7 @@ bool P164_data_struct::writeMode(uint8_t mode) { result = P164_data_struct::write8(i2cAddress, ENS160_REG_OPMODE, mode); #ifdef P164_ENS160_DEBUG - Serial.print(F("P164: writeMode() activate result: ")); + Serial.print(F("P164: writeMode() result: ")); Serial.println(result == 0 ? F("ok") : F("nok")); #endif // ifdef P164_ENS160_DEBUG @@ -779,7 +767,12 @@ bool P164_data_struct::clearCommand(void) { /////////////////////////////////////////////////////////////////////////////////////////////////// bool P164_data_struct::getStatus() { - this->_statusReg = P164_data_struct::read8(i2cAddress, ENS160_REG_DATA_STATUS); + bool ret = false; + uint8_t val = 0; + + //this->_statusReg = P164_data_struct::read8(i2cAddress, ENS160_REG_DATA_STATUS); + ret = P164_data_struct::read(i2cAddress, ENS160_REG_DATA_STATUS, &val, 1); + this->_statusReg = val; #ifdef P164_ENS160_DEBUG Serial.print(F("P164: Status register: 0x")); Serial.print(this->_statusReg, HEX); @@ -792,9 +785,11 @@ bool P164_data_struct::getStatus() Serial.print(F(" NEWDAT: ")); Serial.print((this->_statusReg & ENS160_STATUS_NEWDAT) == ENS160_STATUS_NEWDAT); Serial.print(F(" NEWGRP: ")); - Serial.println((this->_statusReg & ENS160_STATUS_NEWGPR) == ENS160_STATUS_NEWGPR); + Serial.print((this->_statusReg & ENS160_STATUS_NEWGPR) == ENS160_STATUS_NEWGPR); + Serial.print(F(" return: ")); + Serial.println(ret); #endif // ifdef P164_ENS160_DEBUG - return true; + return ret; } /////////////////////////////////////////////////////////////////////////////////////////////////// @@ -802,6 +797,9 @@ bool P164_data_struct::getStatus() /////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////// +// Read one byte from a device register // +// Return: byte read // +// Note: No indication I2C operation was successful // /////////////////////////////////////////////////////////////////////////////////////////////////// uint8_t P164_data_struct::read8(uint8_t addr, byte reg) { uint8_t ret; @@ -812,10 +810,12 @@ uint8_t P164_data_struct::read8(uint8_t addr, byte reg) { } /////////////////////////////////////////////////////////////////////////////////////////////////// +// Read a consecutive range of registers from device // +// Return: boolean == true when I2C transaction was succesful // /////////////////////////////////////////////////////////////////////////////////////////////////// bool P164_data_struct::read(uint8_t addr, uint8_t reg, uint8_t *buf, uint8_t num) { uint8_t pos = 0; - bool result = true; + uint8_t result = 0; #ifdef P164_ENS160_DEBUG Serial.print(F("P164: I2C read address: 0x")); @@ -847,16 +847,20 @@ bool P164_data_struct::read(uint8_t addr, uint8_t reg, uint8_t *buf, uint8_t num Serial.println("."); #endif // ifdef P164_ENS160_DEBUG - return result; + return result == 0; } /////////////////////////////////////////////////////////////////////////////////////////////////// +// Write one byte to a device register // +// Return: boolean == true when I2C transaction was succesful // /////////////////////////////////////////////////////////////////////////////////////////////////// bool P164_data_struct::write8(uint8_t addr, byte reg, byte value) { return P164_data_struct::write(addr, reg, &value, 1); } /////////////////////////////////////////////////////////////////////////////////////////////////// +// Write a consecutive range of registers to device // +// Return: boolean == true when I2C transaction was succesful // /////////////////////////////////////////////////////////////////////////////////////////////////// bool P164_data_struct::write(uint8_t addr, uint8_t reg, uint8_t *buf, uint8_t num) { uint8_t result; diff --git a/src/src/PluginStructs/P164_data_struct.h b/src/src/PluginStructs/P164_data_struct.h index b58ff2aeb0..188cf7f24c 100644 --- a/src/src/PluginStructs/P164_data_struct.h +++ b/src/src/PluginStructs/P164_data_struct.h @@ -14,6 +14,10 @@ #include "../../_Plugin_Helper.h" #ifdef USES_P164 +//#define P164_USE_CUSTOMMODE // Enable usage of ENS160 custom modes +#define P164_ENS160_DEBUG // Enable debugging using the serial port + +// ESPEasy plugin parameter storage #define P164_I2C_ADDR PCONFIG(0) #define P164_PCONFIG_TEMP_TASK PCONFIG(1) #define P164_PCONFIG_TEMP_VAL PCONFIG(2) @@ -24,6 +28,18 @@ #define ENS160_I2CADDR_0 0x52 //ADDR low #define ENS160_I2CADDR_1 0x53 //ADDR high +// Use a state machine to avoid blocking the CPU while waiting for the response +// See P164_data_struct.cpp for detailed description +enum P164_state +{ + P164_STATE_INITIAL, // Device is in an unknown state, typically after reset + P164_STATE_ERROR, // Device is in an error state + P164_STATE_RESETTING, // Waiting for response after reset + P164_STATE_IDLE, // Device is brought into IDLE mode + P164_STATE_DEEPSLEEP, // Device is brought into DEEPSLEEP mode + P164_STATE_OPERATIONAL, // Device is brought into OPERATIONAL mode +}; + struct P164_data_struct : public PluginTaskData_base { public: @@ -37,21 +53,16 @@ struct P164_data_struct : public PluginTaskData_base { static bool webformLoad(struct EventStruct *event); static bool webformSave(struct EventStruct *event); bool tenPerSecond(struct EventStruct *event); -private: - - uint8_t i2cAddress = ENS160_I2CADDR_0; - bool initialized = false; +private: bool evaluateState(); // Evaluate state machine for next action. Should be called regularly. bool start(uint8_t slaveaddr); // Init I2C communication, resets ENS160 and checks its PART_ID. Returns false on I2C problems or wrong PART_ID. bool available() { return this->_available; } // Report availability of sensor uint8_t revENS16x() { return this->_revENS16x; } // Report version of sensor (0: ENS160, 1: ENS161) bool setMode(uint8_t mode); // Set operation mode of sensor - bool initCustomMode(uint16_t stepNum); // Initialize definition of custom mode with steps bool addCustomStep(uint16_t time, bool measureHP0, bool measureHP1, bool measureHP2, bool measureHP3, uint16_t tempHP0, uint16_t tempHP1, uint16_t tempHP2, uint16_t tempHP3); // Add a step to custom measurement profile with definition of duration, enabled data acquisition and temperature for each hotplate - bool measure(); // Perform measurement and stores result in internal variables bool measureRaw(); // Perform raw measurement and stores result in internal variables bool set_envdata(float t, float h); // Writes t (degC) and h (%rh) to ENV_DATA. Returns "0" if I2C transmission is successful @@ -73,35 +84,33 @@ struct P164_data_struct : public PluginTaskData_base { uint32_t getHP2BL() { return this->_hp2_bl; } // Get baseline resistance of HP2 of last measurement uint32_t getHP3BL() { return this->_hp3_bl; } // Get baseline resistance of HP3 of last measurement uint8_t getMISR() { return this->_misr; } // Return status code of sensor - - uint8_t _ADDR = 0; // ADDR pin number (0: not used) - uint8_t _nINT = 0; // INT pin number (0: not used) - uint8_t _nCS = 0; // CS pin number (0: not used) - - int _state = 0; // General state of the software - ulong _lastChange = 0u; // Timestamp of last state transition - uint8_t _opmode = 0; // Selected ENS160 Mode (as requested by higher level software) - bool checkPartID(); // Reads the part ID and confirms valid sensor - bool clearCommand(); // Initialize idle mode and confirms - bool getFirmware(); // Read firmware revisions - bool getStatus(); // Read status register - bool writeMode(uint8_t mode); // Write the opmode register - - bool _available = false; // ENS160 available - uint8_t _statusReg = 0; // ENS160 latest status register readout - uint8_t _revENS16x = 0; // ENS160 or ENS161 connected? (FW >7) - - uint8_t _fw_ver_major; - uint8_t _fw_ver_minor; - uint8_t _fw_ver_build; - - uint16_t _stepCount; // Counter for custom sequence - - uint8_t _data_aqi; - uint16_t _data_tvoc; - uint16_t _data_eco2; - uint16_t _data_aqi500; + bool checkPartID(); // Reads the part ID and confirms valid sensor + bool clearCommand(); // Initialize idle mode and confirms + bool getFirmware(); // Read firmware revisions + bool getStatus(); // Read status register + bool writeMode(uint8_t mode); // Write the opmode register + void moveToState(P164_state newState); // Trigger a state change + + uint8_t i2cAddress = ENS160_I2CADDR_0; // The I2C address of the connected device + uint8_t _nINT = 0; // INT pin number (0: not used) + + P164_state _state = P164_STATE_INITIAL; // General state of the software + ulong _lastChange = 0u; // Timestamp of last state transition + ulong _dbgtm = 0u; // Timestamp used for some debugging + uint8_t _opmode = 0; // Selected ENS16x Mode (as requested by higher level software) + + bool _available = false; // ENS16x available + uint8_t _statusReg = 0; // ENS16x latest status register readout + uint8_t _revENS16x = 0; // ENS160 or ENS161 connected? (FW >7) + uint8_t _fw_ver_major =0 ; // Device firmware major version number + uint8_t _fw_ver_minor = 0; // Device firmware minor version number + uint8_t _fw_ver_build = 0; // Device firmware build version number + uint16_t _stepCount; // Counter for custom sequence + uint8_t _data_aqi = 0; // Last acquired AQI value (see datasheet) + uint16_t _data_tvoc = 0; // Last acquired TVOC value (see datasheet) + uint16_t _data_eco2 = 0; // Last aquired eCO2 value (see datasheet) + uint16_t _data_aqi500 = 0; // Last acquired AQI500 value (see datasheet) uint32_t _hp0_rs; uint32_t _hp0_bl; uint32_t _hp1_rs; @@ -110,18 +119,16 @@ struct P164_data_struct : public PluginTaskData_base { uint32_t _hp2_bl; uint32_t _hp3_rs; uint32_t _hp3_bl; - uint16_t _temp; - int _slaveaddr; // Slave address of the ENS160 uint8_t _misr; +#ifdef P164_USE_CUSTOMMODE //Isotherm, HP0 252°C / HP1 350°C / HP2 250°C / HP3 324°C / measure every 1008ms uint8_t _seq_steps[1][8] = { { 0x7C, 0x0A, 0x7E, 0xAF, 0xAF, 0xA2, 0x00, 0x80 }, }; +#endif // P164_USE_CUSTOMMODE - void moveToState(int newState); - + // I2C access functions static uint8_t read8(uint8_t addr, byte reg); static bool read(uint8_t addr, uint8_t reg, uint8_t *buf, uint8_t num); - static bool write8(uint8_t addr, byte reg, byte value); static bool write(uint8_t addr, uint8_t reg, uint8_t *buf, uint8_t num); From 4f52f780809defd851f9a584f5f15cc78ec5ab6c Mon Sep 17 00:00:00 2001 From: flashmark <15078748+flashmark@users.noreply.github.com> Date: Wed, 27 Dec 2023 21:27:39 +0100 Subject: [PATCH 04/10] Rework after review, added documentation --- docs/source/Plugin/P164.rst | 117 ++++ .../Plugin/P164_DeviceConfiguration.png | Bin 0 -> 69247 bytes docs/source/Plugin/P164_board.jpg | Bin 0 -> 94807 bytes docs/source/Plugin/_Plugin.rst | 2 +- docs/source/Plugin/_plugin_categories.repl | 2 +- docs/source/Plugin/_plugin_substitutions.repl | 1 + .../Plugin/_plugin_substitutions_p16x.repl | 13 + src/_P164_gases_ens160.ino | 25 +- src/src/PluginStructs/P164_data_struct.cpp | 578 ++++++++++-------- src/src/PluginStructs/P164_data_struct.h | 15 +- 10 files changed, 457 insertions(+), 296 deletions(-) create mode 100644 docs/source/Plugin/P164.rst create mode 100644 docs/source/Plugin/P164_DeviceConfiguration.png create mode 100644 docs/source/Plugin/P164_board.jpg create mode 100644 docs/source/Plugin/_plugin_substitutions_p16x.repl diff --git a/docs/source/Plugin/P164.rst b/docs/source/Plugin/P164.rst new file mode 100644 index 0000000000..9627a4816e --- /dev/null +++ b/docs/source/Plugin/P164.rst @@ -0,0 +1,117 @@ +.. include:: ../Plugin/_plugin_substitutions_p16x.repl +.. _P164_page: + +|P164_typename| +================================================== + +|P164_shortinfo| + +Plugin details +-------------- + +Type: |P164_type| + +Name: |P164_name| + +Status: |P164_status| + +GitHub: |P164_github|_ + +Maintainer: |P164_maintainer| + +Used libraries: |P164_usedlibraries| + +Description +----------- + +Sciosense ENS160 and its successor ENS161 are multi-gas sensors based on metal oxide (MOX) technology. They can detect multiple VOCs including ethanol, toluene, hydrogen and oxidizing gases. +The sensors are connected to the I2C bus and provide preprocessed data as TVOC and eCO2 values. See https://www.sciosense.com/ens16x-digital-metal-oxide-multi-gas-sensor-family/ for details about the device. + +Hardware +-------- + +Various boards with the ENS160 are available through electronics market places like Aliexpress. A popular board contains both ENS160 and AHT21 on a single PCB. The AHT21 can be used for temperature compensation as provided by this plugin. + +.. image:: P164_board.jpg + +Configuration +------------- + +.. image:: P164_DeviceConfiguration.png + +* **Name**: Required by ESPEasy, must be unique among the list of available devices/tasks. + +* **Enabled**: The device can be disabled or enabled. When not enabled the device should not use any resources. + +I2C options +^^^^^^^^^^^ + +* **I2C Address**: The sensor supports two addresses. The actual address depends on the voltage on the ADDR pin of the device. + +.. csv-table:: + :header: "Address", "Remark" + :widths: 10, 50 + + "0x52", "Connect ADDR GND" + "0x53", "Connect ADDR to VDD (default on most boards)" + +The available I2C settings here depend on the build used. At least the **Force Slow I2C speed** option is available, but selections for the I2C Multiplexer can also be shown. For details see the :ref:`Hardware_page` + +The chip supports both SPI and I2C. The plugin only support I2C using the following pins: + +* 1 SDA: Data [MOSI/SDA] +* 2 SCL: Serial Clock [SCLK/SCL] +* 3 ADDR: Address [MISO/ADDR] +* 6 INTn: Interrupt [INTn] +* 7 CSn: SPI interface select, low-> SPI, high->I2C [CSn] + +For this plugin the CSn must be wired to VDD to enable the I2C protocol. + +On boards sold online, SDO and SDI are often already pulled-up, setting the default I2C address to 0x53. + + +Device Settings +^^^^^^^^^^^^^^^ + +* **Detected Sensor Type**: Shows either ``ENS160`` or ``ENS161`` or a number if no sensor or an unknown sensor ID is detected. It will also show the firmware version read from the device if the data is available. +* **Temperature Task**: Task (plugin) providing the temperature compensation +* **Temperature Value**: Value for the temperature compensation +* **Humidity Task**: Task (plugin) providing the humidity compensation +* **Humidity Value**: Value for the humidity compensation + +The ENS16x provides temperature and humidity compensation. The plugin must provide the actual temperature and humidity to the device to enable this compensation. For this the plugin needs to read these values from other tasks. + +Using the **Temperature Task** and **Temperature Value** the task and value for the Temperature compensation can be selected. Compensation can be switched off by selecting **Not Set**. + +Using the **Humidity Task** and **Humidity Value** the task and value for the Humidity compensation can be selected. Compensation can be switched off by selecting **Not Set**. + +Note that if either one of the tasks is set to **Not Set** compensation is switched off and a default value is used. + +The ENS160 is sold on popular websites on a board including the AHT21 temperature and humidity sensor. This sensor can be used for the compensation algorithm of the ENS160. + +Data Acquisition +^^^^^^^^^^^^^^^^ + +This group of settings, **Single event with all values** and **Send to Controller** settings are standard available configuration items. Send to Controller is only visible when one or more Controllers are configured. + +* **Interval** By default, Interval will be set to 60 sec. The data will be collected and optionally sent to any configured controllers using this interval. If the Interval is set lower or equal than the required 10 * Heater time, the plugin will not start! + +Values +^^^^^^ + +The plugin provides the ``TVOC`` and ``eCO2`` values. A formula can be set to recalculate. The number of decimals can be set as desired, and defaults to 2. + +In selected builds, per Value is a **Stats** checkbox available, that when checked, gathers the data and presents recent data in a graph, as described here: :ref:`Task Value Statistics: ` + +Currently the extra features offered by the sensore are not configurable in this plugin. +These may be added later. + +Change log +---------- + +.. versionchanged:: 2.0 + ... + + |added| + 2023-12-27 Initial release version. + diff --git a/docs/source/Plugin/P164_DeviceConfiguration.png b/docs/source/Plugin/P164_DeviceConfiguration.png new file mode 100644 index 0000000000000000000000000000000000000000..86b7abb9136094f969dd924cae042d8d4ef5e8b2 GIT binary patch literal 69247 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

?ODGI-{Ag5I8iz$v& zS5(lSleEdc&^A#=7Tm(jR(FSwvsFh1%ZO^1z+d^7ryr*m_eCBIRbf=#^otS z+O_xx0QwV2Y*E|8IK81~LC4XseMZ;5xxls51V$YogvnSc?nCaNlYl;PPVv-CuJ!Q4JsGHveCDlGTjknK+#;ptOS(O3Y{w${MnR?!N@t;4~77qU`n43w!wl zuwQ&QN3N}A9CWt`#0$<3f0viR0w6=R9%Ab<4DbQymAbOWvSalT=F#SP{P+|_OUVoF zo&uS#t%y|=?;WC-wiur+)C=JJ2qKT%n)aarb*u7L z?!(tKp;w8kE+Bn+T@D>(&jD1O4gNJaQ%<`<2@Z3{46d9Vion!y4{SOfV`FQL%y^Sx zkjDo3#p%NBL(^}F3TOM*4{cfzH;1d85^pUzlIqMh;$X$gbF!YQqYagk z3RgOZM&EB_9}A#4U$=kdXtxM4zu?i>QL%+vuY5+z{-{H8t1^D8m=mD-BN;hg1FC=9`PiDPuPX zvnnRX$uBO;hx#i-jrw-P8{R9h@e<=`?#-$R#t>lj+z5h2E-(`vDvp!i+ZI8h0{$pp zD93f*P51>q3+j&r6&bIZ(!4xAhGOZMuZnjp68%h>V1o1mZ5wqWi5smFsJp+Q&~6^< zIGuKgk@Ef^Cd{d%Kq;fd_i^_C(q!f$v6RsT5?G*_2$;6Sy=^K&l4N(`#m6aK{1!i$ z#5(z;$Kl}h4-~}~NBxpcPGX)3#tHn5OXKP^ zz+}=+POBi&MyHMJ?<@tJ%%u@+)Jh`EKiyzjLdqbm**>*(!RW50?>~Zs2W^+kC%23{ z2TuHQH!?1lWo#X5UMh4cl9~e_@4<$PwE<&_=6Kb%!&21sLF-_qh|ioRKbDEc;EtHT z96oRN&)+NS!QbQB6cS{EK5c6?rN0>rZgSn!c*l>+X}ri}c}KuFZ4JlGD*0US0kDU| zC2zTQP1_j*^D}P^kD>A}@Rckp;(;`oj*603+VcBW&wL*yVX|kvQn~b4#AWf`E&&Qd zu<9CcFF-p=Jd@51&6TEQm`0=Hc}Fj);wWBme)>9_G0ovP)5c;h`5MUs>l!MQc9gzZ+U8$M7Z-^Uk?T9vJ> zVtKdHd2E0--01Bpq_wggG0L-giNj~bXXIwMQtvN)xp~fhj?M(5!Mum- zp51-yyJ5MDnz4A9Q)ACV{u(?#7!mLW!&`GHz@TK^;CC!RqXC}C4lYDq>h#+}GhwB+8!{OaKgB0ksNgX(h% z1KW#Z0vqX+zaR>01BECTvxKVn6QZNbsl@0~b-LE4BtTj-c#yh-H@FKHlbYWxRdVQ5 zXHCtt=YAsp08)=jmC)iJ_$@yKEl4e8QHxhVU`&v_rk+imYSzzh!J6Kn>UA5PXvKQp z5?U7|cs^$SlW0eJFPBXMz9!&+$HC@)P$%%xSB<76K&DEu+CFJrL3P%pU=b}p<7+37 z_mSFiyh7a9xa#BQdA71n73!V11w2Z>b=*%rhZ^2O$61-b+$W@kw>OgU{@gf+ZIkT8 zU`lwnn@Ji54V4(&fZsjnOF#o(tbBED@O^N~VV>rH{@x7Blef}Lm+Va~PhmY7BhJYfu6=C<; zwyLeQd|uTof=4~J_yUVd`j|%qRoexOUuQlZk ziu5GNtvtRb>Fgb2IYA8pXdMW@@%`kich>UNi)~~+(jq=H^rCCvS_^E#58}pI^DLvx zMcrrdpJz@mse znzS2xLO|yIZMC{g?ccznRCoD?u*YgO(Hcu{`AesUVkY1UY(sLRN#4_W(osrJOXG3geXPXZwdWOVajodYMJo5~b0c1q z?OUPjF-jA~4vm$T$F~gAFuu^-gRrlr8oA+F^*u!FYP{yjT80hZ;uC{3k|rL2K9B%( zN5R@$?P*z>%pmC)VmMud{lsIKrjMCAgNl7*&N6#oGT&hYHJVU5`fIw@&*8dPjYpsd zUq;@AI$EdL0llJ@8W|B{sD(d__j4hJ;pydYycH&OtQImW{`!YS47v=_kGl*}i4IHe z+>+&`o(c%>)~{wbl{Kf)cqOExsvS7&6MGy-(Ob}Imd$I)r86kdbM_&T54bH9GAr$W zVwGj3R9roH!W(RmDPz_|*@zJ7yxt^W6MoBe#%^wD3z6R8phM!jej&`tZO&rb`kf{C zBUM|M3g9zfB~`Y*`e;EYd-b+&DxONIlW3+NwHlz~V{@rfl6KuEoxxpht^)9?CAugV6|4jTo zr`Z{7(D$8I#`Y?#HPg-OV=ZfYeGumF3W{t8*AInGr_Yd7FX^8ibQG}L7vK5wf9oZZ zRX8<18!rq%cGz6 z=OO7vU%{#Y&oN}`@?(uUq)tGO=BwK{BBJjA_8%e;E^mzMpE*#tOX?Z(l4fkQlTsDu ziLh=62RfeBPi*hW~nGUHU;*0OeoQAgGN|vHIc3BIQDlff20`g3yj z;sexC^tJ9&p65Qo#3A$`zufh}#`HdH_&jeN8pbwJ#$5t)!>U0^NfsRo43vZ!w-sOZ zJ;g0|vbyFS7Z)k8^+iP7-^J|@ExYXJDc*txNaZlvIIb=fq(>Eli+mu}Ly&9FRoS#w zsRL4cq*MvjFgEUUz*FP}Ru(fdba0FfW-Y|f;J4dobg#MVG*RLn`DL)Wx~;0|AhxC< z>Mi7|k&@aru|G^1kSuo6)lsfft&}acl^!b@i5>oY%DS;6AQ+7o*L3r?;6yA4WHIU9 zC&05TEY((my)`Sc!CDGKZ}eji+A#6DBD+(R5sRfe=mXgJ@o=QMBbjJhD&TYJZlfX4QL$?5ZeT>}X%b z6GhYX(%9ls%Li{}PY03JwU6s*JU_&$ivEM;to1cDnk2I%1`GQT`VSm<_kF0bqg`oo z^qW!)Zmh2`e-Q!`T-rpJF4mgzxlv6D-~Iy}>wYM%-mu5Wj>?QE;7ggvS)6yN%mcfV zv_`3YQl|TPy;v! zzak)((ynkk$3x3o!{>j15WSSO95~aOOP7}?Q!yh6r7QnZL4s3ri68N_crO-pqO)h@ z16m01Oh|f}>t_$6l)td=9I6kV zQ1_FR7CUQ)GFFd+OE-S(Xcy;h)Ndp4M5vE&=C(Wl~=?b#BQihKEOsU0{4 z{MmZ6x}7rw9EG|;g>j1%o7uc##w;&_yQS(J2QweC(fj@1#K_8pX<)sfOP99 z561Gfbi0ZFs*ikAN7=?Nz*Q)NOO8uvEm0XvnZ#h&Sm0oW`0|FieK7Xm?G@49ah=Vq zPX&Jd8S|YvqayMz(9W1drVS5$)^ZWVx|qPh^y^Gc4`^#ll2I;Z2Roqz`qDq4{EH7%2g5~E$T{kK;q4b_ldmEU5T4KZCO^GfA8#Q95O5{3$9r0Uk zgi9RyK?nxo%U~mz;q)JsO8-A9RdXS1_!#@Y;bS4||3(qmN1x3eN%oC$KU(gg1i{c! z%?&mZ)HTc!3k5fZ6Zxt3PY6CBf49%0t|sYWn0&`%WR!dku`Lq64NpXwc)$VYkXT~*yKj~!3b!Pl3*_g>_f$RpB0!Z zO2cIGmCWNx_CM{tY=R3i@}Uj<-RHJLMR9nn8Wv2}IYHh2G((fKx&TKua~@US6%o`W zX9S;MnGU;I0?K}oC4-9zTHx&4UuN8fu#b6O+le=|0t0?wi=b-L(T}wjaY$E5f*=+N zGVfa9CS!Wb+FWrf&Ib`wbI7x>)PoM|D~ybzDC>W06Qw3+KA;()F<)S|cBDrwZ{xN( z8C@fz-Wf5{-j$~B4lzH>np(~Br`GgA`!d}2w9rpXE>Cg$<1R5i-vu5bKvdHy@M+%D z7r{^>)jw=)((DWTk%Iyh2e)8k9^LEZ@DG6OSIzM64-X~GNmq^>gM5Q`dHZ7Z_?B(8 z{ECZ0*5Q%|2fLV3JE8Obx{0-*BI=pu-ByaDqrXP0Y{{R8P)N{|Z>l7gSKgozO25}I z`4JG3bpr_L@-NbtRh*Jo`s4SwDLd5EK>j*uxo)^d>ssoN>$Tj)<{fI3KXu1*S$h?R z8eRgiOpQL@{LCU@hb#07k5u6ahNFXJvC4eH=$M{9fQaF}$3x4O_)f+uZE=Qa;vA)P zd_+F#2U>uQ59M{5aSxtK7VDGSsFU~NzIicjf^2RxQ%{O~=%7!tI&oLTj=R=t6-+&} zONY8N7SmN^JByqB*?al=^-0i?Q&T!lg`c=M5%+4IV(!KT^D~lWJRx&55L<0Wlbl;S zP!aECFcUc@OQS$E)d+R^yiP$IF%s>(o?+^wuWU^}s2E9f+CGwyAds-6|oA4Anw+r1S3g<#7)N<$ddfHO`4m`-f{G*j*hpgh%KkJ)X#eGcVg5<%DWOzLlA9znA(Aj77~rmC6xRd@oQDLLxd|zHyD#z1_JQQvlZ8$t zPekj6RGGtLm+?d5whk2+KNxOf~I=bfkFvx(fdz)j5#Qe75V0{SI|fNeDlviLqmotg(XT>TGP-3 z%F#l8WTcmXmaWhHK4iw7vJ*I7f-iGn2w7Q1D8)aO z+~aQZw;rAHwXzfz7C*$_7dAA$x^Mi-wIQ_F#&~0bkfH8giR+XZh7_{@X61^2&`q+3DwC@a|{ZRUeG4i-d{Pd4J zg6>-;m`4Pj7XvrQ+E_d;2=Rx;W|19KPgcn)Rul9|HwK90fxnw>bj%-;6@I`{W*Mn) zy?K(y#us2F$l8W>(L!~=DxS@**xRYtHg0SuX@j43dv04(`yfAAV#^%<_?39-@oc)N zlkWoU=n08u?p3;km9ShA?#T$cS|~hA`pvMxyVHb^~G-(F`42 zQokhV#lctJDRvwg(q(sO~FtN}8I(gf;v;1sxnIH9BKP9Ie?ZC8Ff< z4{yXQ7SNOJycLOvIrM_X9OiiK=w~_r)QVCL^og_yaV1r1 z#SQ09!;E>LO1l&zV=(EM~MGuM^i&{e5m3`rKxMWe)|Jd7?Zv4l7X>OA`Gmi z4U+hmEBg$ro(G{BoswFypv^*?(#gubnlG`I?sM}%!1WE_J{H;F;DDxLL+pAhj}a9-B2r^Ki-`s zVb>7bfP)=|&~cR2t{Z7K_0MYWummojeHjW#R6{#HRw~ZA>3ZiQz3AY%;E5ctG<;-{W zur8t6zTjN+t3IZ)zl<#C-M!@rb8}Jc08yI=?Q|f4K0)}i`if*iWQasRl4u~`!L7`v z*)KD^(ci__%xR4AUiZ#|guC(i5@G{Ii6_XY2>ig;ni_ayIGZlE?>rLie z{68n};0&K>M$JGzHvO7G&D2^e)WCZq(7GKH%=}&o3y?){;0#&8S^SS$H33trFas5~ zJB8!{e=jvmRLCTqJ&kj$vI4X|b;onLiP)4acCr>nF6vr`qks&SPdx2l;R49D@Oqn{Np9`wcL#O<1j=h28 zPCjgGFRMQW0$R1=yHT&0a|#vludonwvP~Q`8gr(sgIw(dpX)!l$WJdYwS7}gGRgy$ zA8b%V%!CIg9V*6rwLTrE#A2DHm!8;cwEDNwD7WF+2bVb!?ZAG24XN#afDZ=UU!wa3 z%vDvDn1#etUG*>5%5=SKzOs#P_V@23oAA=II5x-Fw@w)DtCTn-`M6nn+gDCeUn#{{ zefWzl7SFeSXi72f^iA2=e(EkN`yR|z?CQQ23cFgI#8THDmgEDq@?uS8FY5GFey86KS4^hPNgkOeVT3F(cE@A2j{Vz{v?xpav zh{CeMwoi{+iLOgzi>XQMgT&dW9d&mE`4TZshQ4!(4UNDRhTf)LLSL@qNdTXFIoP@_ z1wSuz8$z|!M)4|Z_@3CUn(LG1vQN6Qz}@s~+!ezruT(wUEyY4Moj@I#ul)wJGH2Bg zkDhe-V1h$TeqT=Sx$nCA$31K_9O`5ehRAL7dU0Pp&kzL@ME#=OTrx5}rzuG$?+$h*mZ zaJ{n!8t5EV;Q$Q`xj$%!f;6x^SKcun({H$Z;dT49C)+JAqkOI-Iu`8WIPuOX|MAY= z%VJOVcQsNcL&2Zmpw7?16Kt)vK&G8!O|wr|tJs}V6PkuN3?WFvjV}wxXPN4(gU=Bz z%>Mw6l5NWjfvL3Z95(onIG(KHyW8!1=SNMn-6^JrCylS{XwP-ZkUW;)F7?3eH*M0} zx9vdffx-eK5W~rRCBv(GZ0w`v_cq^0QVAu)@UYjlFR1u0e?8B&b#FX9tSKi;TiO-r z;bh>?nB%4Hwp}vafAoi_uNt%2o9BFqLl!$iKI^C`kwXu3>Mip>_SV=NXnjhQ)VKT5 zo%E^SrrA$Rr8EEK-uz+O=>*b~-<#zGWA*HZap`*}TgTr{{CctL-J2Ok#BRGK5dWh<*(S|wEM%a+x?BfP z?SHik96%I*$E2Q)rvCsq4ET%{8m6WnA0oaI{-u=q&oM4F)`zZZ#ge%0x>sZ{ zYhY=o5lkO==~IJPbmZ9}qlg{;)8 z9JSTK;aYhM*GoS;lvWrDRMirhmRa`8#j2(oPfa{3Hn!yBAvW0&IM=j>6X4eny6|Z8k;yjetuFoXEZIgww-FMIGZ8_zA zv@J**VMV17+xW zLwNSdmrOOy-;I4a^hV0~`2O8=e}(>l|$Yo~(|Rm#WI47rmb z!p74`*+mn<@DZ&lJ4Dqu4I=n|OUvfG1 z|3-J>RF2e*c$KXAJUmhX*^NJS!ylkJua1qm&P)h)mgKRF%bvb78p&U4H5K=oHT{n7 zUx$sqfH_uFOZJGSS{;-dKc6rA@dkT+qE6A4$MA);b{z`d50FuL2DX4H+&xxaytr`M z2c4V%Qu<(0`C*Resf@*7-)c6S%g_vTQe-O_DZ7IIK}jlcF4orB7OSkZytC8QmFUGb z!2gz;|C5>oA?&{Pt)Ycj?e99J3IKwnq`uWDJ$dX%x_=C`@}-DbYvRIiDLTkRGjw=3n|W8lIvW0 z3R|>KauOouZE}e>;jTG&L>vCRD3!g;>Gf>V8tTdJHm}S>A`2b`*p*Kg1j%1wMGhfC z?p}f&6s-3&x;gKu+tNHpuR?YMpZ4);bY16N7*{*>%U>#E8~#r>B?(uC@*8U4VDMHR zCa)sRKPUq1K;1{*uP=A6$S2sZxfl>En{;FZ+45xIIRq#hE0g>ATK;{51`zZ1h{U*l zG(cupu`E)c+_onW*e7gxVE*d}w>V;)%&qx@%JzaJxZHA+6F-~X8 z^~ut%tJDa~Tr%%=oMT$}QtaxkAnMSl@FCILc>o#0`w75oo1>`T(_XLZi@4qQntNEc3>5oTPE-z(jmSf*oTkO%sRACZJ`ueQ+o=u@TuI`5N8KB z#RnYV5Fp=k51eFLJt)+e&OVQWppq6lV$FtnnH@if^tcmeIbdBmjq~)8wwr*@+_IZz zgH#-{9o9#M^&P;>6?cmCuml49ASndlavQ8+*H#qY*LhlU%e{J}wnA-;^S8ZWnDTrJ zxC06NDPcxU95%Ft$DM}8NR>F<FHhYsKUXYyeVwqo3=x_mU|SXU`km%JzKW)>nr zOuH9I*Dh13cFA{F6|E=QypIQf_`gazlS+DVJ)RXE& zTYV+h`q{!b!E4GXuN|8M4&0QXDXN>n{7edL=XzogujYiQ_h5&RJ!HLZ?u9)R`_<%#FNNQj?KLUOb(T~hUM+NmP3A*KJF5OI!5&?#oEO1|*W}Z45 z)3*3gZM|9l`dH;~vodo}i>5E*R;Ig=>P!BO&7jOob3K10d&}kS%k1Pz8`W&np(o9p z{vykJAydKxnRVP|IotqWfIEG&sCjgu_w%U&XTg2t%I<8)pv^7YgA3g)m2hARh4s)s zsLG#IvR*>9nAilCcQuOQk}WN`#|~cDjq7^;rCeH*dnM=iU2Dsu8@O(ufaLvD3JslF zJgT$v1O-lgO!Sp>O*h%NRJim^q5CqQnJweYQI2)1{T4s<)T78xa^W1lSmLg9skC`H zWEDk|;vhwhNnb+!+r>0avPQ}HlS8Q6OXY1PMKx*&n1ghId(Gh{=z~h5qyRH4gS_>d z@sMuB3)+CTYa#~L8kWgfMER% zWgv&p;TS=A_V>Rx<5t{;cEP0BsN+B+=KkAJn1Bn;C1IP53*vf-aD4PD)2s$42)MeU zAWgBo%}x+Ft`8*#PsX@g4!>BT$du5gOc6<#XMmGWTMz|U(U}!Pr zvIvirr96g58ZOa0Y#fO12T?oPnm4A6rP0W4y=ZRr!M=c+1UbOglR!Z~SkRe&{(W4t z2v|2#?=)M)7 zn>^}<3?F0{Wiq?iYe;*WlgIF#h<0fiV4kpzI-gFz015qmRS)w+4~c7C~7Cw||k>@K>iB1t7l0sG}|e>ZsU7x%=8 zH73HJBcLW?A09oBcmifx3nYbzB1U;^k5L#8>F$m6rrfjy^vhKhCKW zvRpMA%;@$-QcUT^Q4^u0_@;b69|Y@jocVo`#z7Q!T%I%$Ey1K{@pCn$3wdA7AoCht zrmDX$+`0W;#rReFkNRu@`Kl;&dfc&iMRNe;q<0l5AuNmgGCR5bP0JZ%R3XorS-=eP z9NNfL{x3hO%kZnmxr=s_FoekDz_sX?lO29zH(Gc@;g!&}bA9A|9lw*W&IRP_W6s-lG(krY7Ut%Av zCbA~@t;*SO6@5 zKAYMGuuXHNI5nL(!@*E;Ih<|wpxr_8T@k@8yqx+&(Lyd|-ln)e6I{w04j46r-XXmR zLM>iD8BU!OjNVbaedqo~$vytC+M{SlS zR$9Gt;U2N>@{nVFEOwkv1zu8h8EG=%R_+GHEWdJ=+gCM8ZV1_-qn`MkA^DK`TuUC! z-xj={?LJ{0d`3-nirayX@WbPj6Xf?tKa`Jyg|Y6+hpylA+uf}s!+*}`C`FP;=ppF~ z|9_3q1>qzl$6YLi`$?O6vxpR|1W1Nbg!JUBY(-} z_-jUbE}Wa}*CE|+BD!~(3OmuBOu9CBtNksXMn&2BJJxf1G~t=?wTJ(pX~g;7#YGg5 zNzO0RO*VvW{DXp~?L1YZ)*p4{ptUzUHFWT&G54BT-BQy?8ygm~68VwmY=Kaz<4JZg zPq|H^jdjcR=b@CVJpNg3Ao9eYF?+efS*SYu3Qnx%68R}m=b9MPP+L!y8~p9kt@rR$ z-P=q#LhYI+r8n zM^t@gBp)Rh$U4D6lme~c`}zvtw&PE$2ok754O>SGpW>BZHnT6@7eKdJLl#j-0{}16Rm`RBfx z7GrbucJx>E zE%!nR&;uq2QcKL%(1i4{OdNJcSzCy=bY?73M$HoAu@$0BFHNx#NK=l#MuyKM<;<+&ryAD693f{~AUT zPR{Dg(rhsOY{Pi5I@JUvG%~Rm3I7oS6#H?9AIiRlzhP1CCY^HB|E`x#)bDe%-8Y=ve>x>r=)1$g4zy0AL|=l*a5$h%o+?e_tBdJF%qGlJO-0l0s80e@6NT|DaC=v2$glk$@VMqAN>^*EhH! z=vk+JwOHp>d&DTbuJU}*&#$@Dc*j^HD0eN~w=n=4Hll&`m7L5NI9<;q9L2};kmy*YqmfPKYTOqHsCk-&TM*);{=rdCaa0sKbyLUq1GXU!>caEVfXkM@ zsu8Jv-}4)lygL-sqz`R6^>Z*)!qU1LJCm&a-gLdt4HG+skN&ob2W^up#rJ&~_jk(P zGlM?r2{!6t=GgiV;gdN9{XI|<@hWWvY$NQ6rzm!2eqO*lFOGS)#a4}8-E^?!0-~HY0B)v*BnMD zUpN5Ez%kGT-=$XB5jl550JP6S9k;S$<-@M8{~F^xJYheyo%hAihLy>esrXaqM7fAK+z4(RkzC4%vKixYx|8` z_VAb2vNw5?*t*TF7Pb;dzD!F%q8Tlhmk@?D>impaTaTlzl*0pQTgQEnLbxw&#VHe} zqae(RenIEoP+*7*A%`yYP0yHxYbIB&zK*_qa@zlN{(}YPVS+iFPnxX8$Njj|OlYYo z!OkaNJO&SM%T&`XNUBEe)mhVk`#2J0wvFJ>lpd2aG36VCu*g&0KtM~8zj{&m`n8sPsp48L%Ca&XIo`- zVmrV0p7kDiEy8x%(q=6qoPNblS;MTO$7@RXA6|VkDj{Rw91B;Mw9*n<{6j2l!O)A4 zUmARC*<@1F!028r+f6asFlB|1<@)uwboL9tf-J~Snx;zeflCcdsEB-Br29VD%R)G1 zJ=LrEMx4X!>u$L)`>945fpvyZ1L&v{L~W30d{`iS0w>ktUz0>;c~={!e1-Ru^{4mw zZum-?E7w%`$Z_zoriUVy+Vq8KGec1h-4JFTDB_n(E3dHn2<$ORhLG` zf8=kAE7ff8YAXEBa8XlmVn~{bJ@26VwTvJ(&dE$g@@oQ$+SXx^zoeSYnY^McKRzUo zu7a4bc64z9jZs&ja6nwp zO=u-RXjGi@QL}u^Uu3CvqAvHxvWY|dJ;k%YBY2?=r*V_X6Jc zdmYIX04N1nHWywu&x&-wZK~~;kYb%X?=2*c!TtAD(rLc2Eo5c=*CVboWGU0}Hn%e> zU{p9H*MFk!Nsjv;)C&USLjC8IqndW79zy>VFt_LOI67I%)= z^A9SZK|WhuJ~!jPPRBe_ZmXVX-qq@NkIIShY+1!niDHa$;~Arl{JN7lt|Vex0vozd z*FnSamDr=4^qX%|roxRatQh=s+WSmU&h8 z^CpA)$=3n*GzRHW{`b>un$TN#C)E!ATJ1Lh^u`baZ{?VS0Y7*QR)vFjQn5qqhMAC< zhdP;U>A6}UBmIj#uZ%VcM>)Tpkon+)Fj>E2fPUA&QjkV zmf1B~xg%L?`ca)wY_j)NVDf5AD`b{dshC+=YAirN*}Wg`uZjK6!~U`ExoFq5ZQJi^ zdU@ax57?gPEB&`#sgDOBVP71QD;uh=k}~&{8z?Zw<&$YxGY&GvV8FDbPe+Ug1APx7uG!p-$fGU=0b z)MZZNYtnUPv|W3h=wI+WcVYG!TVj^Rby@M(~**E6)?a;GdFK)_5>Mk%q{2yKz{0Hppho zqZ3TFV-Jzo1PH<$z=Tt6ryXS+FfCQ}Nt7#sN0tqlf(8@Q=);U)T{Py{d%a^r+WJ^4 zqsub@|2G?HzLtGUX3E5HmU??R6oc4f3Dk;{HUi;4c#lGF^k zj0Udko85Z&{F0yhwl^d=R_3ff{hZ35qO=XSH8DAZDyES(TCYFi+-%;{P>Y8H#<7*o z(9ACrj`cwW+tF)`zZ(=e>S1VAmeud7CawR8E_yCH{Mq;yY_um+^5ojezBE z+6vU1u4!q@AN+&g_sr_*gl(>#Aiu6u0gpSp>s6VA1oC(bx%&yNnNE7ID%;r=acUQY z#%@pJa#4|krl<&Yem8d>AVy#ez(muRT)@L@>ygqv=l1Hh~E!rYCPkMZR8H|g0`DF zwLDva4Ln?`d)ZbMIJ?~(omqfumNFlv+17OEFYzWR(zd4%LpF(xxLzKhYgV(CCH}dC z4Egsw5NYqts2u%^ca($Ngxcc5jH(G)ue5re@9oyvWCml=ygMttJYC4PwUdR-PKa52 zNsKps&pr3<174!erk-Jjwmi;A+5~x3DqY3h3&3tGkYwdaUcx}&Pzf)F+6s%FC|FqF z0jI}cP2WI&eb{m7F)>5qZcs^jU&5dkQ;+l#9<*rpmBp$vcauHHAQ&m2?Vz)7ATwe9 zO~=a_8Rw|vCE-%55Fg{FLtxUR!)1Ob<`Cc#Nk~XBmIICL}1imN(l*lN#_aiM^z=ZS?uCci|#1z0RRJ zm3Fms>(Fb9zuNBW&FdPWy6-qNv^6bK%dyOnTd-;x*u<8q>Nf&achJ(i~L*kg`yF} z5977NZLPYbjLNt11y4@CuAJnQO5CZ}ka<=}v|zxZT+GEqs8*Yanq~fer1Fb*EOVzk zO9@UfFYYmf-QP;EF6j7J`Jx8D&qP{U1RCm5|W(HZR$4=0QO5oF}h{0_NijJ4rf z(>X6ma*?u1v(5H;tRXL$pVZ$#{fq7QJrMI~%mgic)eRm_OBPy3B*>j?SHGmGeLam+ zpSi2kj6-fC;@5r6gkFz8cWi-yEr9pI(U^~ChDZM&RL7})KYPZRMN^j)`^3_M|7m5W z9D4jg6(je!zP5esSuG1V>(-rF25W%Q-UGe}dR+szQ5Lt%iAy3JlOCU7mrMKjK^L1B z_4C+*84sE?d-BUBLJY$nN*c1~z!_GKe)6O}T=UJ-{g7GEKrHN?JPer>_65N2 z=n2+g_^JNry^xNcR2>h22GqhY32C-=nia}#{RIDR^zqGB$=}qlx50brvI8wNy~_jU z9k}iCSculVVWqtB_kSMs=iVJ}z90cf=s<7$Wlx@VE@eJdD8pV%Wpq^Cc0U_FV068F z2a9U{K^5Bql-3dpO7oY`2d!GP}Q?zpRg~GfR01Tqn$OA zjQ>|4BSjx@cc%=iSDUxZ3{Zsxh2J&I+J0wPlbziK!4mC3?c6_Jl1M=rR%uim{Q1HcR5J)jM*091pNx4FqYhM}hC^Tw21)FV!;kXAsI zd5hLSJ4eV<)O4jl&Z?en_W^XeX+5`y=QV0(UzI*pa4Y`^NfoA@Wg?!0u{;Z$zg^$} zUi?_qgnff#yE!+%MMVxs1{W;=yl;izSQPsC0cI7~q+rF1?i&Y0}=c<G00@FQzz!HkBrhIn2zPZ(W2%r-(w}V^)vqN>$shtT-q|k0$ zx>?Gf>Wn%rG8k{qogtYnJm0XjP>1zj8-LuEq3FvlZq`&qqaLxgH>%c-b#psVq||9m zTH*>k)O>v*$|!-3T?4MaPmZWs@Brd~m>l@%S z`0cc{J=g>Fle{$1z(DgX8b9Ce{eTN8U*hKAkn9XI3GO7V?5Bc_FxW4ma!ZRsk`6Zh z+t19Gsx=nz%)@bs6`*QCx2O!svCRt&-MDruKf1Y1A_Jv|0~nl7ka)M)b`!ooczw{_KBH6sjlf8(k4UxqyuFKY0v?2LW22KkfdIIdVJIym*tgDfAOd z98=4nYG?=Jr}1Nq>jpvrf6(d#bq2^$Tp4lMnwb#l103^tCh{&I&@1d{gK>0q`w zyN`plfe2B9&0Cug`4ixfzwSruYH;U;rwM=xgdSFt+4Uy3XXaqTE}$0t?1EE~coH$Wt8$xg<(krZv81r0{mFq5j`hQm2+`thv!(ILuo&ORG5l;B zI^GE>O*ksA>=O8ibb-~>`<_oqoX-(XG(wosOzp}5RN~s67kbZUUzo^TO_3Io8w2M} zS^d;|u{qb9+OD`UYG45V6%L%`@QG3UPj2txA<#zIv=4(lx5V*OmjQ^K7m7_vSX_ zV!`X01<;q9Nvmls*rBg(3NUfH3hMQWB4DVw9+Klwx233L(oS`BVs&T%H1I5(&C>*X z&|UE-i~;s=AMCg5OGP@k^!^i<*~}iS#pD>{Sw3O!e7L-h*#djZQfe%fR^f6ea_Gz| zhLBsrrV(Y|8a~E91u#UoUL~HSsIX5_{hkmo>Wq2&lF8Plkhp2!ad}168;^o0Btb`7 z?&~?k{de*Cf82kn-*D<8an*J!^Z}5a(VuHr)?AVCBB$0%!~OD4_OwF=oe)y0^g8#N z@krdACUxqhlJfrQe~Y~Jz^v7D^CbXzVQ|2!6I*Q-{Xfd>gMVg)HdOu-C5j-kKWsY>bjs<=Z=BP7~AU%7pKI zvYq`0d6Q}ri-W|4u#EZ-;io#1IcDVlH3vZryrM}10asim4b$?nXzOG0XeseIU* zT{hPWzlI4Qa^pJ5GZ%XUMFFW$HnIpe&C-~HKa*}zVJvU*R-`3XWvlNc+!!~lL>b>M z%9?aqlQ499E=aw%H8X}hA?uS#j(P4A^_-t^q+M>Zc3=EDf}4ER^oa}KZME5$rgk$O zY;6MF0Ue@$Vtg}C-UN4ns6-06gRkOos#?awhXnBJ5SZQ$Ah&J6_DF>2_w{#!<_s6x zINNX5WrDiOk9L_&nUBpprENy-_D|76bo$uXC@5y`HYEwpS;qNT1W|zY2=)FtTWosvb zw&W*TT60mzjL+$CW0K4n_fUVVe;T)O(bkGP^FT7;r6R}vsN|dZ+q+fA5u+#>eXvrv zI=+<3Z?G$t3ky3R5q7#|J=v>FVNxZG7M%4ctJ*jnf?z@LT2`ae9{x3%O(-||SaZQ| z8La^Sm;V4 zWVSWIh14ZgWJ_x@WV_3%|J02(!L|X}RjBv8|EDNcSVwx%>? zyN&6>gt{*hY9qJuKjLaK4eN??ct-y;!OB0xrb3DV9sEEvU(3#2jpyrD&HEiDH`m$OOV~xQwHdS7k#~AeOqW8kHXcPM zx;4$E;havU#UrF>Uc{a|VAE^Xu!~x-tnpQD8|wo#&IzI#ZYb!{GXDAI6uIk>gbE z_qF1BW?2N2_FC4DA6PfPU~^qw<|wA`(>j3E4T*ZKdpDan1dw%U$GNT)J1~Lvg{0+Y zr%(RLi||5DZWt5qB;YZTcer(k9Q0dmt0W`mn~=lhsV=V}{xw`yiEM?ffJw^_9*C!| zE%`NAC%$Y|gCAD1<}Z)HPR?P`r|LGv71F}aHp4^T zWufx7Msg->Qn$?zY-W7#G2?91`J^%8p!c%}l8Qw6JSJ9XLE!vKOd}H_vBEMGD4oA@ z@!BUwqYXJb<-RO6et^2z#3*KtoJRbM4|fR(b*-d)YZ`4bbZ8Gul)PB0PYfI9P?t0c zDkoghevB*pW93bpy;+m5i{i;7B3DX$#^GkB&HS8hMl6I0Yx7vb$6tDv4|}Y-?uI{9 zBU-{?s2R9fVLT=FWS3CGcD@hjmi(v?-N4z(Lw$e{A_Q`?_+J2Br9w?Z3Mhb#|7mNu zN8NJ)*FI}gvq}64{C`N$YkbU0y8t^|v<|!pF}yhYiC3t=bk?n+;ekNxmV#6DBi%&( z-hSiT%O^k}TwU4b`jEdBqt1p|T4a5ksQ|`(9h1{!4oaeg2*9HH8sCC-F45@54ivUU>ZdP?K|w2PX>dPQmL$lKxe%&Zg9Ce>0e{ zzcJNO*%uyr2dZ>87uB=G0S$PMWgCGBeG7`=;|jUN8WA2j$eB`@b>9hUJzAFN4|3l4 zcBw9lIre&VeSiVc?|J)bI^I3)-^7&*SA;0LGjTloY=uoCy=c~ypZP`xD5QDcmtzf0bWZZpw3 zbR-ufz^xSy0I3h+`|pbmsE(Olj(3jKupEG`Zgh6|pJ((49;*UOGCCLIJ9xFslI|*R zzqiQH{5((awR$FuG);2)Dt0-6i}Re4;i}N!V96w40dG5g@$LUh8-_bf4b~;mvH60V#JdM_CgM~`2W?$>Sck=(cLJ7=6j^38Ji2)#r^V$}ync3n|h zivLBO>{NMD1Ia9isDvFOWav|7ze`ja{znSdA+<}wHw2~6nrTUiK2@-9p$j(r`UmNV z5U9)>WBrx()X!?Sxe1&h$&Y$BgTN5cJ@a~)vP=dBh|(kpeTVAU_G@9c`~Mqy?f83l z*60z{Te)AgLpQj7Bq&he(JtvDn)NljlXr)5SEIoMxy9WvtU!C zs+d8!(heK?t^sNBfxN6XOcRmY7si|`Ksu|Z&vwyBKa~HhZ_DmG<2*CCh&7^#=sUDe zuwZ=>C||Aax}n?;;qZX&T$Q1cZMlr}snQo>Gt6hd8A18RQCFHFY1w) zR|zF54D?C-cK5>hv#H1R!Nfk)K?N1OzGN5ZP1(JM$A20X7{OIf7fq9DG2Xl1O&UK8~ZLXiF0XTo>n05N}5)exjFl z7pJl$)cvGy?w#@7sS@&LGzE16&JV7&(>wKSOg_h;lALFUUNFg?$M|Y_MK)2lUh!0!&_$hd+eOz%PlbQQ{x}f< zZuu_Z@W&o_lE?~IqM&s`5|=Dfg^POea2Gdme(6=E*UU;X5Gs`&YYULpY`^pNmmaEM zj$jI=VAMllt?$;jc&!@NyQlJMX#wz@pDi>bIKhv_^^NmRmUcirdaG#b)bDHq}Kj0OXk%*GkFpl5fK5-hmQMjH9QBmaCnMBg>SdX`mc3+a|*1wZKnMq zrP&QrzoSzCs`@0lThw`BVr)`d=*7lxSk#&>wM(i)1UEI?SRh@J9g!@Rb9qssUD=gU z_}~7Q0;;t^fAo3N9N8+aFyz1Lk^TY? zhz}!nloE*iQB8q|(&|Q;X+zw2XUH^qF8%m3M=7TbHr!OGr zEbJk%1ryfBYd9YqGJhy?zth5oDzDZK+SDn~tC}_YrnL9D`{<#Ny*0a9m#4*&^EC}& z{VPnA+1i->4u5jhuAs~xoD1KunvIX3c6~y{uHsEO1_ceg*dWNfD^K^vi9iyF9!(dm zmOSP|Y+FO|LCyz%SM$|RV&O?a!K_R-mB@{CiVi^lT{k-ap4)MMRHBk^3g?*Rka%s` zC6fjg7M^rr{RzJRgLL+;s{CHo9Nc!xQP)C|Mgk|el!Fy#+fsV;Qu<5(*w?)!OpJ+& zoK%=YDiCS3sf>unfP3@;G&Gp`JDi)5(au3+SEV%3DZ8ucZF4!2=ZgJE36wg2^|b(I}AoGLZppYP-c1Yc?S=Bk8d> zZ7orvx1LNNxtDr~aI-V1dCF7mYyfw?5(Cd`Y9q!NNfAiPl5_=>qOIJjR!=ucqm}2m z1RID`nkG(JCpJSP}K-@H+r7%gwXTdRd4yMx1q+k4B;e7MO1mC9H%y~ zLKvG-?H0|2V@@uN4zO55ZHLbqcz{gdh!_?kjqX-EE$7*2-pSjQ27d0tbBFQ`>uGr_ zI~Nbs&NYZ~$rEvxS|bv5SAH*Awxo@U5?0MzG)$i|PmP8)PUBkn=NqfXc2$y@hnMhp zsE-QYTF7PY!a?FXRMxj;fh0BkW8IRlPPY?6`-l2r5v>fD_B6#X+D;=rM@@oNVCGqI z8&whiiC{-VTxO8PBV}%k)Mv)M*hr-YHw%U_YDnS2DB{41-}GM&n0mr8=P}r8ndj zgI4R(BeC0PVnqJ?Bx8Ty@y;Qb_0`Ng5kKh>w0AWwfMMu$t@P~lCU4&E(Q$k&i5RuR z%0&A>N4^D#0qk0tYv6Np;G=tVcy`a%9cBMH#eGVK`nfr}Kwq{}oBMT&-GsBBS|!MT zW(eoN_olXJCr8STey^!9*K~$XnZ}=eHS~gmU!n$?zQq`5?4nxIF@RyCQDJryvJU7v z7BH3FCKALlAo73_B_NS-T;suO98z)Dhd z--E@?ck*ILo3fbPR=?<9iobnxulgl_Ks)P0g6dnJ0=CNgT2@J2O6a9@H#cGZZ9Lvr zjh&z@SBXC2SjJ4*kUq(c5CSGup2{zk3y>tOk&Vm^xjOt1$ILc=*1S{+9O?|bh`Ndk zkM7WSKU2?qOg1R56hr}}Mg8}FVbCu9Sg2*^nA9?zGvpTQf^m$R#BT0`Rr({PWF+_J zSRBMKQ)6gCC-=x^^hY`Yk}d{Kd|!lphVJXp$+02Zut`8`zx!w_13#U+P`#JBC&~M3 zRN+-~g)3Hx`Y&yc8$M}O-o6HKs|x0*4^~Ug#YxJDI`s%2w3DtxubY)8moFY%l^Kvn zGOoU{q0iiuO+3cvu3HREhy1t12tAy0T^LA{G+iWT(5l zaok`l!2P(!yhE|zF@9u)aiA?kTBV!MQ7-|fe8 zOz_0ywZqM*OODiex2=jI4DYe?!}F0ym&h}HH#v}ydJAwr-nqqd&E|Wz?c_*L|A8hN z>CaeXCU|Sxf=e*2pxJIY*SCsGgGgRrg3kur|W`b25VCn?$TgO*zjjJ~=J+vHvBBFdal)hMf|?`8V| zTX)=|Yt%vxx;eA_A}=9vhTy*pR5P~rMjXIGEDs7g3hy%17MYo0eqs6vY>6-GO_q(a zcCM6`>oudicZCzk8@?1h@(y$U_yQ1Zd8{#$`L3TmZtR|eq821emZR#t z*x(SKguw^6d*CJ)7DOvrt)`;s#ovcYWIqdhxo`KHNt}yr|7_73)5l5;LcG+-Asu<( zr^O7=`Z#B-TIn7^WKX(jDQnUPNw&s~C4W*@cq|$T>z@D4k8Ax_!9Ki|doQ+~II32R z1w#rMT${&Mm#jXsVcoPmDwi07yf=j#N!gbc<7ingC8Ez0E1?SXNbobARxjk8EAA&` z542fF`36WW;t7TeC#u%oCJ6fw71cPDNmMithfxnRfOv?_gm%`J_1pA0Y>Y} zwKjzpr~F^jU*=2g*!YChg%CyY#;sSW7|BLv&Agvo7s^~@6;t1@#Eyi)58 zQT0x)4TIRt(oRmcnH!IF$$jJ?My9%i@&-ES2+BAv?%#}DD>7TxrwSH5gSKY3l)0T6 ztUVLD^U@0Nm0nIRdpbS zQ(;M$YxGT|vrb%5NH)gQ+dU3WVCYE1HO5^IHrkJ<=ehO&?Gs_#Y+fUXd!_9Z-{A~j zVAAlw&ENfiwOeP!=FWzQz1)`~{KQA*&(RgcTJ9%2wmvx1aIUhOQ>x^P^s|0`;RG%j z*j&rm8FQ9Zk#5!hsS{1&E4}E!Ctk8I?^QY{p01#-1|96~luUQ~0Hl9eE3qZA$lUXv zYQHS?>&0rhr!bv6JfM7zWn)ZBX_1pXmEX_*z_=eUzPj=yVwjcM6JhzC;`Qw88rTt) z`FAK2lvXb8-t4TT&L^!OGT`nNtll@Htl?KM&~PIahuTX?MXAV9RCM}=?8FuZN3?E z*jLIjUvUC|z&9DMq?+zkklfh%Z&Zj4)iUb3W0TBqR_;{~@Lw*bab@Qww++{ZP)O^l zc@H_sIj{!W!iu?zdX{Y#Hy`{ZiPN=M+R%UCI`xaz8p4TwiY4m_&k8iip>G;_I}ZGDfK%ysvfk7^^QM{nJGRYx(e zAbv@Zh=R^9*>X~cYVclpugU{7Fn!OaxF50Pv ztn=X0#kOB*!zPI^C_e&{-;={8L5D-_B(q1N)*UPRgWpD+(yeAYAt=PXKurRFQ>HBl7|uEi4>6F0guRw zZS>cjiCS5d2fm*kdSo_obie;(&^adh#XCb(gMPnh*6n>Is6m%Ejj}$j@)u5Z#lVRb zK~s@1Em>^ZBd;9o)x774=zdvV2Mz>LuC9!Es(zwq-0qdx=WhKvX4WS1k&^KcCX5mJ zqB+Ls)xmUe3j38;mMQzQk-GLPLZ7;{W*6F)pMKt1w4)!Dy|Kp4#^*ugEi=a8o`+gd z`2{@jrNV{tYFx=Esd!y>d~%2`O36ifg_-5tGEa714HD@B#AW-VEJa#a4o7|dwdch& z$~}MXmt0YI*{|g{t?AzlpBDxbJe>S**}#BJL=usJ5blF+>TB3d28hP0nWYAI)-y(W zugVb#UJ1Cs*)C`r0YzBL*HZ~1W%K&}P^%Y9BB__VDHtl5@)IT@Bt+9+*cSM^wY4U7 zP#s{~!}TP_L`%I}V0+PtUSKF1ao9Dx>{D;>30D^B7gejU^bPl6XQ15r)niN(8H&M_ zi__<(r5ya2lF9QTc=8ypYR0m!D}=N9wdXmPvCJO8E{uQ`-ZZ%LDd;_!4UoT$yBo(- z!0L4Bjf!9SP>_F^tL|aky(rf5Pp1NK3y5d5rrcP0iMEN$2><9I^;d1l|KaK_gW7Dr zs9&tmLV>opQ``#0U0b}6;ts`%ySo)A8rAMVMO znPhIszSds*w|KTo5v)ANRPh)mA3^qTg$$8By(oLIC+9z;PuMEzCM}4vWpDn4(r`6> zr*{5Z19@YxXxx02cQ}c2dLv#!T&Rb&vqe8RNtk*qgBC6|;86bcLB)cq9(nX3YfrV8!szvf3 zxkF{DxyC~AvXZbR_8;ec(mr%0n`S#kaem5vHWMbv`A#%B(r#jm@SU^BV;H!jA(-?1 zaEi#zlxx8sWkiIWL`u=PL*o^!gTy7x^xi98QCK{UoGhjDRnKsRrmHcXo$$t#K# zo$lF>m8z3bcj9~G&#aT|!PG&;7;eQ7#W@~J#P~l~wc@FV^JjhUpRHuV`)uGPu%I2( zM|<~@4m>r1(1AQeyTIBT{`iq4TF}B8HTmzqvt`>wqZIDS@<#ICRNK)tbd!zq$)_Ws zp6)y#dJ5}YE}I5CGV&`-XY!q3pDxOL;Z%0Gu~sJ{`Eg_C@;@ee5&$Vl*{Y3*sz zKYN#`^@y<8uuKwM+81NI(yFE0EB4OGM)pdSKflh)oa8omV^b{rKz2 z=T#utwk0(YTU6RZRpp%$Wkrz!w0Y}?f+Bx7aLu^Zajc=mymZpvE5pw{*T1NOcrr$0 zRWYi{5j#{lQ8J#FTPsuNtlEL46s37+-*Dv7g2Wf$HJYb>tYic3SYD>&ndxTDaq?t1 zL#;4G?ue^AGb{IQ*;Wd4b0gbbk))p^zr@~Tw7fVyjivmWZczvJNUSvw;~H3#OPYfE zxH`3`UtbGGQB~fd&OZhGz^L-UFWP%WJ6W1!ZrWXUjvbazH?b9?Dd79Wrt($l#B@ep z1n;UcfW(vVw!cf`aOJs1DM6Anrp0pQ3S&_WJiTh;PYTxrQl`Uv6tnqNBEF2ERW#-Z z#N*B-Ds@bA93S&IG}z1?QBo3dw#J&KNaiN(wO|lTg2e1}3V+$v!UZdtNvfthjQ&qJHc%x*v&8825i}Kg%xhWYCV!5~<$vjnkeiuL zg%`z0{K%OO{1a{g6`tKGF{`Ovpih=)1o^O49o%+z(fj4q%LIqoE zwk6U?gnXB;>9uBTYH%|CRC}+F7iO9hPSJT31eYFPTw~}+uy9!APbJo*wS=hb2R!@% zGb{Tay3-VyQs}rp_AWLV($>#XDjj&`vh3p~nO2`EOu?1oL|Dt{iw8$X>r3vkaI{@T zG|qUJkTdkj1RnmscWzH4Zb>F^JGQ?KS&|B)Y(my4AX zSS6vvJHNcEaxf$dLeY)>&@X7h_3BH3rIz!s##R36>FIwe%i-op zjN|9TKw>^-l?8aT&)UEI5z-&Aa{e^Ycfs(=O6Tt;8L~7= zo0Lc*PK%hiPuNt-TG<;ly{LV`(|Vt9NK2mgK}HM=N+evnAQU9rrG1-6NE>}y35iuC z`R0%k3LIWbx|OX@lKsfQeM@)sun??-4^K+N!SD|Qs)&m2C*1@K;Z|{^h`!RnGRq6x z@cEhH56N<%$|j@|kAE$>D$SE8azY^fKumS?`pK%jGGMQUO4mCo>(>ydwuCtR8)m~x z=-k8n_2~g19)WUv~|3|%0n)wVSy|cozItd z{b$~h5sImr>UxRwbdj#>zXU`NCuToQ)f)Qia5APOom* zhUq=DBgz~B#TB5_ut79qY;cDvtwh5gW%61D{)EX^iA4ukk^aJ^7fXJd>xzkRos~1EhTADazK++A|K7YJC>C}*-$mVaW`+E^i zP6F&S(QN|A*8orOk|#ISdHho1f4e9pqfIX3r>}l2LYS{mBJcbEzipGSiZqg>M{eDC zz!L9f_xmF3Dm`kt&?fT7y^LGo+`H>k@m1c9+;?wY)}skNenqtj@X0vpeX?L@ehjBz z#P4sQrHTiwKhSQq^ipM~c_%~>>wfj=ZhbxX~wY09YH38789{;mFLJ`aBL$N0DF`@O{LAtn9r zo*PxqwKJJV#phToC;rSQ9lAiICzhLYN>eG3o}9FNEaWfOqwMeJ)2N% zTvZ)aWQzZOAT0c$sYs!@uvz#!_cWv*_}#jDAb@YK7y2qG(k$mg9cQ%N2&TUSVt!XW zc^2=Nn*}cLrPFPV{5%wV8uu_+<91_%X2S1uU>LBgnDYVPy-+{jaSfgWk5n)AM{s2F zP`|76JbPxf8db9VYLYmAwg4{5?W`O^GJF75|WEE>2MKDe!Xw zT`twcs127b*D&WqgzZ}QvWdno?l}XwJg8g-%{{FVSkOPbuN^wfeW$!^2}Q-2#nitG zU>q{@GaJhBMAn6FQM`H*t<*ob1ayxf=O@~W^mv=O3zR;I7Z`d1H3$jihYc=9*WKx_ z#WxKY2enIp`t_Ymm(nhjXi<}|4>IP@*$N;%Lf~W89p%jsL1KI4XMq=WgA4sC4ilq0 zEmG29>X$`}^p+lT4Dr*wVy9V8AbU5+RMGrzW7`PkDw=-E;cVPMt91n5lG@i?SJ;{YHU3?K z6{y!D76zL4CGY}ydMM0?gl9Zx$VBBub0SgsgZH~wLloc&N}K7`Uph?t0^XVKEAAg1^$ac8mRvpkL!#O#f_Me}BhtD+$&c z-qx$&ax?>MJS?OReId_bYt#(Wse8{PS;7ncK%MEK9*FI^^ zy(POzKonO#({SPAoZSlRNP>QJC~6t+8g7`7=|7}GY`<53C%>9;8r@+#b)mv(EJ>eq zLkB~T<7%09iEl-IAn1ma$txq9Q*UfC>TPr>>uKhrgEcbVVAhFkocj$5@;foaV#4|} zl52kBeM$K2p#2nxQ~0>_RMBP>b^{-(GqR~Io}472M)7M`(Xiiehg{VN!}W*u28BX85{Kav7fpy9Nu z*ypA%j)glIUDlvTSI)w~!00H^3- zD|M@?Pj+jGwx2uGZk-xN?%%l1lgs*XP^YV~(FAMinACJW*qj+yhOdAAiA zz)*W%@r?-s5zeanHsAQuj7ifDB%ym*5z5`FyL9{~j@n2P1Cs>e;a+MOg?KSNvnabk z0;iip*xb^=tl^S1%@b&R#tvyCz%>>;(x_^%3WZ9wm!$Wx;1n(DozHsZ!(Y!14gl?h zKFqWeVom#Yg+MFsY`*9Le?yB0zW+nYKd8s+er%u&^beRU8yPyNSKV!p!Hw*o@YpnK zPSAn1#@b^^&yZ1IyXadGZ*{h-&zyG178>XtmXUL@5;lM4q>Yu?ax$b1kH`_^+#MPu zqu>%Keq&&*rpfojxtR&XJH&OsFL-T&T%4X1SGc@TZlnA4fv=7sn|ZCbuh5h%NQ7Lg z`0k6`{8a#&+=2|zyaUx_Oz~rRt2i@+Hx*7M>&Yool7jme1%X#>j`gtStv${7>cmIVA-KLWo-6X%+ zSRTTfm4R@dZ8xxY7s-(-nukouh}_#v%GTc8<{KquY#&URip$=)v6mI~1~`?{agOsd zsm%B(Qi@s+6`wD79v9mv$zZi4I~Q;fznFlp|GrA?n;~}ENxt}-wDHcfT5Gd&rOA*z zY=Ig~1O5ncNq_RYD4Z^W>- z{GY`Q|733g(GLB|wfDm0s*hJThC2v&>)^_###>{aTN&!Vmjf!)z%rcEn0)|3Yo*PR zMm^wFlnySp(9U##EAP+N=GHY0n_hrf+XJ5|!Qh=q!9uvt?zjx?X49(-?b7_mJSJ+djT z%VqV+rr!%Wv-4#{C~f46pBum0e`fXF@ME#l>mf}^&x4c~mAR0_m?gjSoeAa=ID@uT zIlMWKrqugfD)6u>#ByHtHUm0KN zWXe72j+$eWe?ApqDY91~TGP>Ma)bzJh@6)SMmkBIxW_e0ej-1gmjClr&#HuC>d~9qE%f#b<3? zUspG8vE5<}`h1(MWPJ?{8dmk1qIlGzUV`WzfP4%Z-snEOmcQD~kmmtqDAddB!HW`4 zaZ8H_X@PNmR-D|^U1Yg;nd1mWi0x?21VJp$MfARE!kDw|zx_jU#JqJQ{*Gb%KUu&L zAq!-eS-swBjZGYV;dsAyP?4KyL$%P+*_NwtH5D}wV0qx*rT7nt)6mFK`7;i;;Q}5a zlH5t?kFdiVL;A@?zO0M-+Q}8l0L-hiw?{K8da@vc?$iL3laDQz7(c1@BJVGTUi^ek zenv`FgQ#P#*A8AdYTIMPrr&B|{pqv`$CEBIuyy}l+G;yxHAN7$h4l}K0U5=mJ^l5| zdwofGa@KnkH}2-g^brDxXW>vyX`h`fsT}OnrNX)w1O#>&$|^dJ+2M)L-Fmj-71iR~ zbB0``QcSq4cXE)Ih46f%i>@7hE2JJ@BIf=iTK)rZuIrR@*=)TovsY*KZ!;D{o@^)@ zp;s=FrFw%qS+O|Y8?kvW$$A6+jB|MXj{9Ad9??VUY=ORYN4 z6k(EY=PUkj;IKl2A*gU_3xH`2ebtp+VIW#${kvKlwnx$XFjLgK@rl+z?@>v6%#GYf zy_NpE2eo&OV9lpL=qA74U!3ixeD3-dh$9?7uP*e)GAFb=rlQeJNGk>>3Xbv*$sp%% z4%zKRF-t!U=S(6yEi?`Q@#>gv*C(sNVRw5yym7xdOv%+IdF%Ah{b%7>+ zOY@SObs3sF`BIQmA^9lNC@QN7M`TuKvbGcO6Mn9c2VQxPYRiAewr>^@REpeHn&dE!c^Rbu}u_J7p@s<>Q%VBG)P=uo9j9& z(;=1ig17Rp0Q}A4hcQ@7vZxm(aQczct4{7UlNTCL_X0*I>C|e&Iu=G&h}{OH@A|1S zO`2}J=nT_5ouEV{zPx@Cu3>AtjdVS_uR77h;%SPe)(%(QcHg_B-`x1i{}i7b#XHyK)Vd3qEOl3HMa|Lj#;Kc=e1css^02;;HoA;Fa*VSx zI*=35MwB@PSIl+N(zMmfaa9emKwD&#-fG64qQ1@h5TF~xb8k9Y5hi)7=vErjkrH!} z0JqqoEzO(a&Wh0XC&*1?Uo{z-NNU+nFM1>k87;BKqg&nc_bVL_2Z|1fmP%TE$kY<- zD^|Ot#Us{15xAzXo_B5&7%v9iDiOIycu}R3EK@%nRM;{=SAb&x1pV5*bkB06G(Wr9 ziyOP1gGx7T)( zw)lzX)GT>Y2WVXv;6_l~8tP>VE$VEK0)&{v=_AbTCWB;CoGGT4-Ws2Hx$oBF0~Dk( zxqAh9HTN*IsA4R2qDNb(o~@@^Nd7?bCfK7LU^2~5bzfpvS>Cj1guE0BJ8A5Yh%Ce- zw!^Y)YMF1u=YG(E zL(&AQ`8c{nZR*bD83#12#ctDB5POn0kyHb+vxRvDLDMQm0zSsl_}`KLpVup&g=KTZ z1J`UZ#Pc3$5dkJ0rwxI2BH-QP#+elEh?oRPBpWC4w`np;?ua0*M56O7eGmcFWf?uW z+k|Y6lVQXlMc{bP2{Gz_tfwg{QLC7&Tm>m~&%Gm^ril+MIAEQ^^5^pl zS~FuerS*0lb#Z?wrWQKRFCpHYIPs9$!{4wwi)ui|#{{#}gwk zR$-^Wp_YG0$9C!hM#o3dIgdCjMe!ZVGsEpVo~#JA2}8#(nB9o~!at-{@LC!ug^shlhiU&)&RkP1`teEyd5ZN25!r$RD4iJ9c`2Sw$~t{7 zBbDU+{5npD0q6kzVNSdCQ3N;QM=HsE`1yuMjVLNAk|T>}6{v+ca&;S{EuR;gU5`&! z%Yz4e;;m$)*V?HaoS%5B01%dhF|?QQ0>alA>lbmbvuuW2Gkmwi~#QTtY)u&;}Y z^lglt-@8o3R>4#U4w7rs7K96&HqAzkdz2iukim9xIhvcti$Ko|`5xYiDn1JLV0#f3 zQg+ntMAf7^j!IHUN(3kb2as8L17mH2$4zGKOsQAp#2&9EgE!45-j^f1dsQH4&Nr{KL?s(0 z?!Mffh4_N2`EV6?p&Hb?lsTD2s*@!>;k0qCF780TPS#Qbvd#JAA z7>fiuGP9^Na!;0e!fqeo(|frHtkYWahYTphrPTA3#Mg(g!Qto7B$C}|U9al~;WKMN zTkSbc1LwSv_B#?sgV)r1>uVipaUCgrvFV^nyp~~bRyB9~m@r^^UZDDh*_#qr;aRAh zIq-m!3~FPD0&!Zg%N+v9eKcRdb{l8!4CofU2R;@M>%1e6^U;c7=aS0W*Z_V}hKMrV z*c9R=H}ksPvDt#ts~X#ZjeqlB(bLv|-JvqzJkN@LjIWDme4p$kFh?%B3qy_!ZIQ@b z^8!6-Y1bMZ+dc|&&q85+Yj4jdiz%Z@tF9J*Ykb7f#ydpb@F(zJ+w0e@2|2dq#gIBY7z{56cq~ zQP{KUC&L9%le5Xj5@S4H|8<2m!76FdHqV|^ zYZt1Xs|qkG_FC=m$)q-44pkZ9v3}2^QZ)M!XkpM#u5t`pN#*wg8q9#Ha1SZ5o91e2 z^4%?)l<-3^#79eY4Lvz}{mmBIt4|3WgT=HT*a4D;y0|22gG@bB-U=D4Y2ebekuqz3 z{`%*GtmbKV{4v%tTV5=iVf~6;YJcI_tvtqf`NiDH#%3vd@7;nfeE5T;8~tprW5}-R zBPsW)3xptX3tOw-n<%teM7$6}@n>)tX(aj=L=~XFm}P;m#w#ygc8uIID879{)lv17Hx#5=9@ zH!9ij`h*y}NEZn1)|)BDwa`~SD`Y0GFp;kC--i}WiOrwBCNOE;Z`1{mzcD16$dlOzxQ=p0--u|Tc?$N3T}`wKpeCvfhr2BL za>AlmyD{snoh$io>XPVzc!2`6R4J^%=LXk}lzg$fr$+3nP=vY6#B&O4A&d16Uq+9@ z^FZf9#sec8eoRwcFm9nPAmgI4!7$aiob(#4v_7_Ayifo!<)UP(mj zt8H?8;tKL^;>xsKksyUtWWURS93*tAiupFb7T<%UXkbeE&P;BRf!uCMJlm(|vGBkU z@4P3ddK91(a=+LfD}fjuh8vNrhTWlr%to@N?Tch?ogu*%LB(V1L&y%k2?;MbF20Y+Y$w>hjn46NFGw;Za}(t?Lp zMJ7o;sw$CKO>%-F^Lgsbh{T{YN|p%IDGDXjoTM}ePZx;iorn_z%mlHvJW%Lb4@G%X zptHraiW{I%RJQw)IVmlSGY32Dyy~|$OE9G z1-id*n96(Ebo$TO%BHW+gx}5crAi7So$L?$s4dG-5mq?a**D<@N&&la!Wz+WCqXRU zq!n&9yTC@h_>Q-^H|4CtwM4|i0TBQVp1B>o+vJ6vS?^hPx!s~4m|<$kycD|JL~h7K zo~Rc^gNy;zNM6RV5>E(+&$PdK_-sNTOpmjy)^6OXjd`Pla! z=0!Ee!V)Yy05sLK_nPcSi@dU8!BufM8UK(F;U6qDea14RyIMJFi8(CWHo*cz)>RSD ze__!XP2mh-`kJ95J9>JkOh##uHgEtdZ5~hwzSEVuCzkLb{go2VP0}0H{oWU zN^3vuiBciwg$}Z3DIOTD$kkn78g~+Ln)t$qkwN&qKSaqT$GQ>~@Fw=!Q2&;|c*zt! z&nfHiJF4+i8f)XZ03ux=W2AeTU`Pr#t)QI)4M913^C=q~jRx1+&`W@_(eGe8qTX!a3VIQ+6Ef11VW#;Kp!U}k{Wf(1@Md%ODQqZ4@@`5l_No~4|?wAZ#9zM2MzjO zR}IrIoMqE;0tEv#1x0NRs#NfK?A2ZReHJH#j%l#nA3n0pwu6EuwoC<_v2u9p>=dmA8SxI{la)vJ=xKg=PLH_>GFf~g?ns1Tb;01bxqQZEypdVNZ*JRw$zm2u>QIO7V6lLvUOWx@9E+SttP>2 zJTR8=RZC%?P@I$~acXuMMcd|Zb#0X{-$@HlrC9_W8IGH~dQDg})75^RgbjK-1a)AW zr7)KsPDfQq{T$%i6Vqq?+X-z2pjcYy!liioJ~o1%k5YUsD7r~+RtcFI;n>HsY&H%K zq!1g@Xj_Z~K#jv_$CHNEglY9Jf@=NnuOF3R_&UVQ_&-p@F$dv9Av~+9e+s%9WK};W z4zwH69zk?Zn9OmCwaqB8oyvGzweKzTXRCb$t0q8`Ix|i?X+nPqgt za4&7Nn~7DmW0scJd4qB+Ouc=c0#&p~M@m*oj==5ES!SPk==ey4_51-M8-qQLr|dk5Gfp5dlR*V_a$u&kveH_WTXM+?pD0FE)5O^m-d6Qzcs2 zHmfm@ttxXT9t$DsPxb53HJzWFoZ@D>F@4{wkInN>K5FxoCrDBV3C>GAGIXb@aIP0F z?PXdS&;9BXOlpkQ@`4_1dao6CH`nW=Ga`DE?LglW)9r;!@)kg!3hg|t>-ge8{@@b+ z{R;|+_ZP#~ggO4q#XkAZ$o6ubObQvFAD%J-hzTmShs81s*Yv(CcRb$iO)4z$h-;z` z?b0BjU|J|95+Iy-A0TO|OyUKOc^)#NCqiNt#~h^`i3ULqQv#IeNK@8WnZLDeTBv$xAp%S1 zeJM8dglzX_F#8|aBtXXhC;3r;TLA0hiy)~+9EvL73 z_B}zplz$GAMhbOITpeE)-J12jE@J zYlgvPCqbrED$tI7s+3LPLe|JLz!jRVFL zzE>J>D~VF65+7yU$6IqPYU3nefNyAbb|$n%KAc?W6L$;3?_miFJc zDH7O}=}f@tb&b|srM=rlGy987PA&~iOmX1L{&DwY?SP5C5u4J1>aLbW(yFiU;x`|= z-3G2Q5;dlNpP#MmkHROU_myB_olm=J(E^*D^S1<7kSAKGa|t0I#^c}JRs@BMFHtYx z=b`L4xaHnBk;HPnwD&~+d`S5rLqx<5!844_JGa&lQM0$*oxK*0A`1e;oI&RY@-5{F z$UiBN$P*6?i|C4~ofJf>2l_E;6uxuM6?>$-5F={Su&ee@Y6A_5pZZG!8itf-0Uw># zW5FU_ROeGsRqPxcqMl8~4KbUB($@{{&v z&)(=Hz13PEJox#3SsxjVvkOVJb^EsT_-Y-=TI<3}v&w^DgI+Q|77M3c{&A`7VZaA> zNo)@VS6so}tCwU-O_%gE!X?i-#=_AfpR6D$U`!eT78yU#8!TfjB+PI(3yf`{O6EYj zqDq#{&=3#+8GrC?5kZa==-KdB9}=KoLd#*Q#f2d%yTI6AxTA47^SKIas4b{u(&}x^ zTw8K?$==?)I&6~HMHB~rQS9)msypf>F+wgso~Rn=Vmv<)<1WU=$a~nEJzr_#0Y^fm zt=wuiV!1Cl9BqX4Tr|#XCe{PLL8QX81M+39)n zg_odsgS^@uVRFwYhzZ&HV=p$l|2Cu#aZFjEpvyi=3*YNC1lQ1-Sg#p4Ig z-3$F`NrpS#ZAxaUlM+O1{&LQ72jFdE8m*BY0gZN1oU74QmMB4zsGQI?SxFGABHxQ% z_*ot>1K^o%OR>d$V0C&zsoNOoNTbqSs@XooX2U8muFhdB|w$w9bwnZ4y<|2Xw05^5}gy^m-XZ6)kgxJw^jh zBbC%*K6%3M+uY|cnr1YXhJ?9}|f?40b+^=TZI`pX~wRNVRQ z6I*miUh(RFIv=B-Snbz06!F(>AV1MO0XDWcn^v8-y9a&-d4wHwY{#W1mnrZ8)nL;lPndfK@$<jtr2<_s}|c@ zlpOhg!xHAtc$9vwYd5aNM8NJDf1eM-N@*I?gQF9Wu6g^s7CyA3SLexd-=^68f-NrO z*xRX$J-6z|N0ZdEh9n*D@`A~(TH8_hMrAKTmNL$%-UqKychPO9E88rav^JXlK0U#I zKU0EAumyecTI6{Bqkmi^B+jM+v-2|dJ%=mgud0M^*(~6+mgl)e041~bYS5!)RU4+( zAUZpyDsD38CZ|3^8pwb(0`c{RMV?Z(#O@NL5NXMfpi%zWpu~qG;ITJkm7R%A&O-Uu z1nbblCOjFX9MFy9O0o>n9=wL7m|6O7PN-11+!YC(Tv9`=1}a^mK5jIxXLoJQ=@(;q zqfdfIDn>Rbgg4{O(6lT&+8=_9M~0?bzz-u8NzIZw&^}Il)9z0EGC|JC@<>7OkoIjM zYP4#>=jhXi49=Wa-f&G#0ln6lJ6hdDIXJ%D88Ns`O#cxg;r;$BORG`GFu?pMPr2xe zI|>Qe9oe1BL9Xuo6rCJ-0IsoN`eqH;vZ2nWu=+_lX)N;oIN3rRL@b7*2OM=)bfQ zP&seA3Rd%?y9&}-_)0F))lN@z0H|{P0qGap<*0Mej=1eoSjIe@u-R?vlg4Q^u}cr; zXMCJMO&$Gcca+X=qWrH9oz!!A!o=LO-Wvv;mp?F7pB-;g^9~p>ipo(V()cbfI2S;* zZ7D;Cf*Z~WL(9oQgBq4DL&G20wOkPPYurOAtaR7*MDS%7x(J35ptL%rqfid+7u)&) zd|62pLd7ouD81sHp}VM*Ow|58B&){t-#?`Swy5$T&yA3h;q|8~cQda9#|lFt*illh z;l&&k&BmfZ&0X~v0C_9f{)|AO!VM|o;(j;baC0vy;etHluwT`8Wqfx)Lvx3fU@Jq` zez$~G&QuV+lZ;$Jw&+Q;O{asy$uvtI>vTC5(tS~*Jnbe|Q<=hc((=pBbNLKO0E05D z%WE23k&U6Q*z&oDDu-|T^Hq%k7*3g5$_TC6wy_CDX`-TQrmrmjDD#Ob zirQcpbcaVdZq#F6Ao=g#R*E-IkYnxH;5L+*NQf)IMC1`Jf`Zn@xj>JsmmB*%c%`Y* z$i5XzB+K!Z+lVGiw=EuHSbMAf#;t-4D*>RkQ0PX}Y566EPh?TJJ$dPe8BX$X2E}6f z<`K5S#};UIKlud)E!7u82`$#hLlLSikLh9?ukcI@j&%UQ=}gGENQFMm@)W!z}%ThJweS=L*`2ydI+YF*Nbl~AGH8=PHbB!}`#U|*&Fk(SNdup?Ea z>>k)fjc{^y*6=i3rZfiaVra7<^femgn=`{#!d?xdu)DO3qbHTE#K`hq7CU~D6RYEzVEYUN940=`7!sT>k?)dx{tghB*!~2EQI@7 zsLggCMg7kq!3;4>K0#RDf`P1fPiiwyj2}QD{W8cNNhyMR&AQjwqEZJp8%Rjet~I#l z%4N8ze3{NYv=^-hhUxi7xh6rjZj60-1N;J;skNpEQjE|g>tI=XPAHKlZRL=Su&&%P zz?7Pb6{TyaDyqV-y<;D~C|GS_@F(HuquE8{5nyd~l^+$gpr(vBm=_VPhMiog+u1qq zSDP18k!-Ssk7!GoByPs@?2Ovx!l#uD21^)1%MDZNF&PHpblxY2@rihvA73afmA|1 zDTriBOWxEc`Viv3o|P1g+IMX1CgE z@i_Qo63(vOd2GUM%yoX&|HQ0sMa9_7ff={J`VYd|@8-I037y{704kn7bg!sio9E2Q(Kz7;cD>v0v+UOql_x5 zK!js^2D+k4_XqU6@^Z^D8ccyNmt~lB4y%jAdNIy*y1I*r`4|9HT2qEah!R+T&u>72 zqER&=u~&++2{5H;G%aVUjBi2hi##wAh$31siJq!>M5Q>ml88JlyQ6Q$N%|&hTjDmd6djZe3HMM$3*VAE=|}XB3Y5~F)2}rPu~8XcHB=)pt-d) z^0?$!4}9{jyxC21iNp+9$D{jS0B(xy-$3x~@O@HG@=`Ma4BSP_=HH6xWTQv>6 zRPDo_tc8W^7BTD{N4+EUFc6>dOWqiH``R!XRQ{(A9htoBOvB-mZy&qe@JgWo1=*2` zj^XyZ{@7^HomCau^NHrpT)dnt&kea5Vo?0slg$?dI7G zoZ?RL7ZJU`tfZ(>2Y^o7r2t6O!wEqQ(!_QLEMvl#VeBq+Qw#Sg#OTmE?T}^p0}n#j zg*aB6r#1P0g@_MWLoG^qRfj364idG9s#y$oD@>HsUL+%hw^Ef6BCX4DqjmTT{m50} z|BhbvD~?T?;^}@7>=j#d{OGcNd}is08XmNb9%3{8Zj#pX2@uQDkv=KPgv5xc%z{Ck zMH*i(Ed`*BMDjKAbuq{denZJnM;9)b+gQP}&i`he@ zyF&9t4ag+c*h{Hb>~66M`8_=K*W}3zf9CvXzzh0`NbD4;_@vB(`Fq6^hnE%XZb$Uf0hy@KJu&oD3+{53J!S?8#wZA9an8c)5`kui{k&x-u$P?7IjHjtp6~E4i+p zhumdaIUHO08>tFDTdSWFMUa~vRDwwQNo#BrTRM}KAuKZ+rucdI5H{pvY}fu?N;;E& zNO0DG#Ih-)Vwo2#IBDomBDWbVSfJ=o)lX8bVrloU%Y4eiO{p$zJ7eO}fq}^#(Lm*K z_g~eTR$3|rjKB`%afw+vB+AOZSZ@Q!h*sUOm(ohW^?^vr6iVCBIe#R&?9C?#<*U7&=^H(^WB8e9Z`6LWSm8=#CTO zc0Gz%O*xzDh*-RrG%SGAzi*YmDj>+ncwPPbj&%_A#r(1V4sJOhwv{BlCg-|LTe8IXFLeuVMyNkzaMExg);P zABBzWkV6(Hwn3-e?m6DtE?l@UUM|jh=}BBSOf{g7YD?WVpikV_uSY*X$oI>3ox&`j1xNvnNJfq7he5|q2Af3qC$^eDju7~bieEzPp zMSeC#4j6YK6Yu{anSWE}juXJ2**Njbk*X{uc-h#LOx)7aIak zg*%?3m)|flMPh}`7MAaX2bdRS+5{*95qC+1 z2uIBbnZOJOUyUE)7dgn>wlSA?Mt?6!CBCg1Jg@HfE`GzifNP5jFC*#`dK43{I}JmZ z?>V@i+XJn{%HIe+XZ=vAZ|ttS2J(Pz=wBpt5q(>PE`hih6bD}Yk%@ADLym$0n9R%V_x{u34CRHDZ+s}l z{^DBPE?qjWN6TfCeyYd)?d(`dN^7y$UUt*YUNy+IJ*yd&6(IH=j?&d%uem2-bDLv2HpoeK4Q-H2r&o3Z$T{zW#luY^jj2 zIN(F5Vyn~x-gf}qEn2#ng;>$T$oWvc(81isKcohQHIfCs4XZiWGc-!ID~02>Q)P_! zUWBT&OSSMIq&bvt8&x*V3 z-(kEnvYQje;ZGs#k`$5Z5qXCd)L%gQ!Lm=60#64ZK#Z4j~oq!+gn zxD%$|D=&;dS3iYygRBX25tXz+92lx#jj>87>NElwX*EB6_1UW2J4zZ~i+RC|e{l03 z5}qjAKcqRL7n6>*jg81_s;JX`mDy>(IlNp5O-KC(?6St@4MK6%25!Yexciw>7(Ry6 z68yCi6HUOEt10}zfu%1fG83QU1=9Rw(dw8P3#mF3S}?|2Sa)p&;msi)ow2NRb>@`};UNSo<)GHz^}53ZFe)w-CzZPbdQB^S zF|P8H6;emi=Ia=BKU*~iOH#R;FFggy@WuF%XI~n`R{986}xu=%QE?%aafS4hhoS=koP~N z%ZUJ?x*JQE(U}|VfOf$5U7$%fbUkZGTnNx47Vrl+Sw&2PdOC~c*|TFgFPOhH*5;F0 zvl=ZE{@7_Ft2DixX)A~D3cK9Tb}gB*m2xlT%&$7_aUPJ8YyF(W+Rr)M`r$ZBFi9kq zrKw>yOHL)yYMSqS)PMV~?`KT%Vtzk5(u0#u%4H)LzHR8_0%ceOPda-eU;X@#?7#N* zuNoI-1({juAiDzM3r*MiubRMgHgjlHeFYzqk0;D=5CFl1A1+E5_{T z{F6&!zc1aw8}r2WWPjAVtR(x_e`R5xXJzO2T>5aZ4cC?N^c775?^h}d!HREVWxPTE zkR(k!>0g=Zd!;zA6F->GlJr^UQcE+ zE)?4fb-#%)QyzNw(EL?fD)d>u07p_>GCnB$QX=ACd(l zMd{p-U8lbapf1VXbeY;hJXB))lp^%o_K!bSpS?AQ$5s0=I<-u^bFP`inXXH-U1FI7 z*Gi?oLgELtjenJ&K$I zMR#HJfivz<5m-)1`6E4pKQ5KrgtQGa4BL?*F9P(Rja>=}Adp6|OA>%ofeb0AkK})L zGV}raq!{u7{#UIAjs4{B!T*;nrSR|ZfAg&o|2gOHbxk4|a^NHXk_A!<^8zsbITQF7 z!av*rU?(W=f407VOT$wtpZUplPA=7a>-WbsRCigL>@eNDHLBms$T$LO%vrDut)|b< zXhLOwK>g;LC~n}Cz(yE&T-~=Rwe|vW00j4oO3a`t@pSQCp53inkuv=yb`m_V{Gnb= zS*h;Zv?5nF`ZU*1jsQ{p$@t*H~$y=lvHRp zsBv9~;>uLifQkdjCM~boP$HwGepe_c2M>ghQ#|Ha!@#!;JFdhZ2I}Z5oxiWRbyp}& z==`rDIb7zol=?M0w`1#JuQR_=_T-dG3=4KBq~m(hl5WogkOU1ClYrHT&Ji5~i6+L9+^@dC1m&hK- zGfNII_`xK8krmZ*T8X*#hDo^oCzQ)MI@6uu>ld7jXE%rh+N6)7NUy+0O|5t?MST0F zOyx1`k{guCoFwi9B)3PK;spzF_Ycq}U_y+rxdmT+HpfJow?#Z>->Z{ZKU9>Ock0@gdz4u%`kp)MgPRS>HOma(9NsTTfn)pxH3QNS&w#d4!NqlGK; z*IjTLt|UcIjCyIJGF?>#E`eP6g)})--XKFHwYEAA_N=v?6!*g!`vgkJZ!3K3->P*T z2tcvGad1UZ_aGxPO<9*FMJO)oqUI<%TH&DF561R~zp1u@F5m0DM`Rs=*@ zGEHLf9ckaxG*teMAU~QT)>m*!%hqDfk1Xa=>c-DWQLh(K4NPg1sOGZD{N-Ly-AYgi9vUuH zJv=5OUZ<4QnKJcB{TY=xBJIz#wh!uw3+I8&Hn`3_5V4`1qIN-kC&y~l%g=fHdf-8e z8g|+}oas%PtdXxT9~I3Oy^=Gktfrhm^7{fZ&1mv>2g;alTS7vV~j{*$0q&aJDtk^5_yY?YfT`otC|8Q0YYlZ)U427@fO_trX;*$&m7CI&WR5)-bkTfZlKy55#@!bK3_ovpIwxwlW< z{AScsN??KhyK@S!W?6-j6pp{^m^o`=uJ`%HEWW&eOiRRF@(A;{GKhzd_xos4IWBB+ zYwwY^kJ&?%j;1~IURCbZ%=A`;=I&7j^}Nb>-$=W=v#mk+=axdx13sUAG;^yxIw%mmqTplP#mwGWbyBT$K=a8fs>m#>)E(~3eGyS$$ZJxK3-uXVC zwL_tUOO*z9&)`~2XS?a6`US3N(xrWLp76$T(Ix5@#u`2-R%h2-a=RTyG`>t7$#T&Z z$x5(c=K6@1<_y`1nD>x|MW=xd-&-ez0B6xB<{@nzp z$q0662MB4blFQGt&^~J#!>tHzF{(d)HS$3PP7cLaUHhyL&F?qP;bsh(!gWCo65Vwf zucpu15z!Vt)=FAc?PhxHVrbQi`s0WstyF{R3`>lT>1mH7h3M;a!;ZP{>eY7gyo<)0 zqWn~=1+|?RZ+j2`yH;}RYViZ$R?d2qXju+5cLTo5ETDpX&mn?Smr zX%xRt3#);^GuWoGzyESEzuBKxExd9l63}Q&f|C{G!8-JwY%B%Hgh`~(4y|<6)>s*w zEeL<1nc&DJc)HH;q)ylbJa5mtQ-l;_d zwb9nsM{%)f*85y>p144vdN8=jFb0eI0M6kea+M3de!z6|t+>LY@1nC@y2_l{!Qeu^sm44P2XR>{4?;L_3BlpjW}YPkVyhv# zHG;lI5cau1%~27=-h80K$3G%Bl6XBLr3Vh|DnJ0D9Bn|VTWe(VD;yvT0L-^>p|1MX z4W?RbeTmKIXRIw^Kro$CIR(dNij_~_H|6yg7V~0)dtY6aB zx=#M}XGPA?gy@vO$TU#XUu^i#rZxlAAPvsNP$IP;*P&jTlCAMUFZ9Pz3?zAYFv()a z1FWb21u?}cDs3#j(V*_gvu~>)R}zlR%QJK;&T(*LCx6&8K>9ZU6~LC7kp5x;eE}(a zt96UwsW-1%E)WYn-iR_FNd0GRBcuey9K`e`AMfLbC-o}I`pzS%0w8@L4+2fi5SB@u zrp4FRuQj)`X39m8kRuonhCZ=b9^D%$cqw1)GY^{?=hK}Trk=Sxi?{0h0Tp3C_IbHY ztb1!U{Dq;Bavi3{r|zw-euIrM(M;<7-hPnE+H(o z%7Ie-Q87Lu2DGp*9ENgQG9w|C5Ocic#p{Qc-ssR;6n_z7QJq0qszh4%CQJ zdZMj$pEz6Yexnsf`uaoUHNyCNJjNby8X;e?>lbGS7dKz6@^1utB39})S`JLSc4G=N z5KfKGiR0&);?WQ3B#@b~T#Efr8UEtn1LZhK$na>@#U@s*8=TA}y5)yPSz3LfoBKH` zp%7EkQDrqHC1vk~^S9pW6cUWc;Xm0I(43uh`f45r(%y)q z$mw!Qo_$>7@L7qO9u`aOw|p^fhQ04EVn?lAj-{ONg>qetny5l2?)-}+SXjBD?!%~$&9nQGVG}`xvZN>?${Ltj8Q@q zChxF*wScpYi)jL(9UASOKFK&eQ!NyrQZ0E<7JjyFmZ15!XXj>$5VA`#D?kDLutJS) zwP)vGOkI=CH(P8mW70A!xTb89uRQXjLZLhVsI*NPEX_rZGvoO^f(*Jv{ku5wtzd;` zCP(8H)OE{v)ua`v0HI5vY2%nmB@vIbkaG|LXwnh{PjsUEGL|!lKwomdO5g!ddFpx7ErCk5t$Wc=(oMLP87Y$rfweC!X z5dkM#lw5#vTJTj4maC!6NKGgEZ|0)YJ=3DhQ9YPY+SaH<4U~J%)8Sdn&p&cf^doSs zuD_A&;M}q1;t1(XL=w!-C0M5^m4rpj*yB~xqx?vM@r}*S1W1bHXfht5angcw&wVnH zy3P`%q&!ht2AI07*z}O-o$}8}G8f&x2Z>T4al8!`^4(V59q#WycFS7YE+~L`VQrB0 zkS~BK8G(P#AX`HJeKPUeb$^lrgKF>p#?sqv=zHeVRdDa&dFESAPBgi^3?Y&du@8Oy zNGb^oGjF;bM_}uScZ??D1zo>;DK|`AyU)kbp5FaU z8=$%VTdVj9&HsMaKhbe^i*aDo3c;v{=$Ey}TPf3B1{QQPr{98F>iWk)Ifmr^j&qWt z|9lP$TNl)iGbwoJ0ZzJZv71!4=aK$XVxpuXcAD$r^EZ_vlTppD#O-?(4(PQpN_9&P z+V#{>GW(H#!rOi3dJ5+7252Vkq9_(;R>RVDK>+Yw8WV)&-m6XWu87Ei)(cx&Gk(3o zL5)qoxF^?a3hf>A0jKOV70E!#nWkkQR@N?NPW|d^Z6!^g@}xv|vUNpWT~#ID{!Ld< zcbFu(@a>l=Iy08aoUwed7q#!btSVRMn__bUYP%!OL_VE82xjmTK~7U#a80?(?M+kt zrf5NLmnn<6f9TJJ@luFyd>I=+IWS`3>4YNoc2aE8Ta z{s^j*x1{IU*}Ymx`nfS6Y`N=G+Z#ZifU2L-LFj%e*q-E7Xn1fpm7G}jk)8o2o-vlS z2f49^c{l|UIE_8hBAEl7<7ZY-Aw-(F(rIyzZrbv%z!iAi^J%$FT%tQCBPVmzZoQa6(3}WVA*x)-h>)RXk^*NMfk0Uw z7ATwLnS(+kTtB{@CLwwfAa#vYmWEWCfVk|k>Ho1@iL;bCBkCLk6 zUgPbc6)A7D{dS7e&70mD|Bp7n`)2vO>;>nK)dZ}AqZ6I?)nqb@nZ&%M&@WYBwBTmgh~eiRC4zQg85!?QQQOl;B`|in);qg)@bS9QDfE-_)NEa~ z5Twd_cnlH=)WYWx1Fxz}w@;5VdC>che#>jRL4#SbuD`c7H^q)>b2?~VGx=z+=uap` z>Rb?Tnk6Q<+>!alTJEYRR_mQ9_b~1z`Q*gnJ)VT{gghd78T4h`n|BA<=WKB=NfQ+2 z{!$BLE6KYf*D%|xRwTOp`ev0pR_D-B?8u;Wg?ttq{ABaQTz9JcYtsk+AK=*t<3Ky~ zB?S6-CBw&bP;}q>e;(d=)bL~dzZh5PFZ`2$g)921dlkK@rX@>f`eYKN+yQd#`g9=@ z;+91PSU-nHj5)Rz)`oaQXXfJ51LP^?Cs*o8QQQAm)}Aw-Pa_V&kN%S_J8FdAZ?(iW zO&`A6J3aI~mcIz)a9WI^a8CeD9zh?tT?o~I8dr?nkB3D2f7w&#`=6yV7T`LxedeT@ z6beIQAEt36d7d>l)V+&{Bf&Xh`?jiFxS{N~uJ?1+tN+b1@-2OSH(j+1qA243uv#?L zMb=QewQKrVCOC{KzP|OWM=9-{tt=^!+0wP~g>qR1Mg8Bf5|KEchco)#Vr{6o+>r=} zGYH9jF6;@*{|5WKij4&o#ajW>4;5ICCzUyA{n-HQrTObc+rpxM5UGwZ4QY*n8y?zh zMWyTJAQtCCwlLK*&)c~{9w8g`v`HYgz)l@=i; z|BqQmOG{-g(vN-W(T}8JO#qX?iFQ?e*V>Y8{R679XwlZLEYNpVoeki6U!CCknN@~vkMN0^K*=P4Q4Rgzvl zQOwKTe?Ul-e-j>AUpJyLetU-nB|YB{TEq+tjLIT1^xIVBq6D+*`L^m;Pnbp^v(jdN z2u_=vc!OE|b9dpQ-a?wDl`@5J)|0mTI*lU2Xz0YP)POopb_e!rV6^uVzP&7wuILIv z=A?wWO?z4$W-h}fQd@1J###5?{wW%PP^gpm^yiX1|J0P3acxg*q#NOWFFZeNevWsO zf{7L*zQFWjxSHhAn1s%QtjC)~cVVxYNDdNvyTi%F7xG><(HSK|lzujRPEN+amm<{DK+{F%GL355I*oizj zhkV=uO0IBsz(;~5JhtKHsq+w^o#w6o# z1mu6~wY-~+j;u$0lr()My;Jzqsk^F@Lqr1T9DWn;?JOyl{x0-UnkKUO%Mcd&(fTK@ zfVq56UGww}^KA}sd`#+C5@&L9%1?RiDI&K(J&bKO~K%v;);}y3j0FIbN!~Yjl#1ADtq8unphWCG0MSen)ha41Qfo` z9=6b&CCY~|J)1BVLc3UFiDzIS4UgNA!USu>54@jDXfauEjOMCvr^0$9%X&RsH0=fe z06bfAp*tWyQ9Vsf0j=~o3Qd11-B~e|*wHBqbg+B%681xf8FX$ zBgVJJ-4Q$Xwe4Hb3ERo(Fe?F?rug_wo7f%TV zB@L+WhCdUAQt0Z?C^NpHwa#T|R%N=PQWHcwPt_W&#}LDeT)(26IoPnd=)EZKNzx^> z`!S0#X9*~W5C8@BPUgraAY;K-wbs?9{H5X?%OlFRs`^yt7u9C|9vK4w!?Wi94;T)P z@S3QRrRt0>ewb;}UZpoTpd;!Rnm#zlVnE1VuRSmThjo!TP_1zfZ=ZDWRb`7puK~v~ ziJ%t2@Q?XKCjR2IvlztT3wQopaZx9_ZQiFqPX)64`!@U9vE7t z`XTxLMLqNOgmX9lP5fdBAG|18p=?RW@ODz3Tyif5ty~}vQf6qVC-OGYxjgI=@}#3x zeMbm@gZS;C9x&uL5cK~|Z%DX}6!bg=>)UtNFd~AO>)JLa9sarr9{M+E@i)@CpxFuJ zrSY7z8rW(p)9xh)=CiabAK~P`vKKmO>m%WRK)#xp<_8r-eJ(3~Ad}Cm-{vbbv7aG@ zuxDLOrWhF#y4len7YDw}gmF4s0FeHJjY$@Nk1Y&damNPNY(F7=v@J$=7y~dh-@=%Y zjftgvvE?DInek@jdjp~4+VF?>WfFgpEZVkPiB7xtFi*(a$ou%TgYx8=v!{+h*bqq$ znq#u83e!cbLC$aHDBPqh9?ZzMBkr_0|5~4z$l%Mu{KWvi6m?^_W#tq7?PwRROQ5ca z?@C+LqwxxQB5bnO@yQ>*&uEguzad}YTOY}0AO&Au1Oo~1^MK2)+IqmcVXgO)-lZr{ z`=+rZu~)C9Sc%7_SI3^sanF{hvY2B1pDJ@m=8 z0D$ydstQ1gyjl6WD24O<4jbAj>6MY~I#={)0F)7up~CWiX{(kr1AB(6fUMWqnNh#s z3MC_(FA}8PI2A)d#b>UE&cajz^M_oIUb8+NW_H27o4~EHU*>K$}fjDBXpV_5_ z<8D6L*!#*vsUApuA-!3qi<~>A;L^zJd)I3Evi60&$y?lmr*c7^c)PUT9MaduWyKwq z05eF(-ccwCL)xXQ%Aq%{`x{*+XTCygT_5E!J}oK2-0Ju5TtXg3ZprVPV}&;|yI|u0 zuY;+qeO+y}I@$JPC`TM-zd}Y59ncTSnI)Wf_~A!r6dE8qxps?eYhWrh-OgCjxRcmE z|7x$r=>p{EMW4K>*sEEi{hJ+{*!0zX*o+=f0(PuvkCBU)IwB8ut}4bTUMx(qJ~p>5 zPArUVv2=;EZWaCm;vR7GTUDRA`mObhn@NyF!nu_AX<>jndkdm_B6_Bo>W9voP^RXo z%(U-)w$D69cD^fP`sJ9;D&KA(q*)!9lpxUeTy+L{`lWP6-wUoO)nK`LS)O|+5Vw$x zZlJH@WoIcw|K9VS$NO2hVN?F+16HE{Xhnr!Wzq(GtNl?#f*}XujrhgYh(N-2b6}ybxD8* zQgi78s|l(`T_VUJPSzPEBR@=Scibiqwx-B=CMt+9sK$nIvu}R8qgU zmAk-|ZiuW1>s(uN7NU%{dH0`3wVLE1ZO zQ&m1lM_E*dQNG;*mr}jD*{*Qge^tb(Z^PTf0o$=or&Is2!9UX!`;_lg8$FtT`&_RR zcsZIRZ|&ZDBc{b)ojBKbpO=K7{e#_Z@#_!xw?1gfw{mZ0>0=J=i&TP$hX-CoX)*ajT%wx6W58Pm+kW#bEZNBBVF@)ge&`; zdHc2Issp39C6zPCuPCq!JzXi;P~qZk)IYMJ5+j=3PZ9UyKVJ|hGq-O)u6zQX>u9zu z>3CHM!~qhkn(FZF?BvWOC~a%L=HEM!C608XH{JD&*BDR_U570d5}CAIVG^D{c&@3| zHM*cgaC^9K(=63fUfDhqU03Rlgkowr;OOH9mh8Xw6CPjNk_@;xr!1Q4+JYPD5Sx5i7WPM;31vKU} zj4;yy%AkC*JA>imrn63Atq28vdlSzgZaQ5tvnlj<5FB&?4@LORVX=>FK;Zm81>Gr+&3@I$te>a(J6 z3%Ga0&{+-Q^FAb663HEQUf@r%e`nGOo{@119bNTnRf(aJxD5<_cu!07Ww*}4nU)%1 zfO{k67OEccW_7=54zKj-mmr3e;JQhb^w!Zk9?YY$1TAFY-Wx8R@Oxg_!O6-xSwVsO znZ>(E&(R|ibORsVhLBI&ved?$bCueSXB`8nt2ce0Xy^0UE6n-t&m>a}sN__Q-b$V+ zYeuc^FS6>Xq?%KwS}y9t?Li62e1Y7-?g}7Vg8UsL(DBu`&|zTVRV~M1d-_RLuZ0;| zp-C}VC~7W-IGvm`8YN$Q1u78nTi-D^M-Nr&d+u(S%T~DgX($X3Sr;itKF)-Z2IqT zuipz5jRJhatcDHcEw2HMi2buyny*b+uqO7NY#1LuK(_VRPTXFQJH}N60(0e&h>ZQ- z@spAV%b>Do-~t9h>lduI@lXk1S2E%8>5a8Qn#-Vl*4B+5N^Il!Oh4OZc&x{-e#L^# zygNdS4?qUSzv8;tOT9buUlZ@tn-PAsy02b8N6c%l3khHe0d0|XZI&&0rb#tK`sH2L zT?t!1j#UyN>^-?_XZPHArU}WuM0(v!-*UTUULR1mL5d7l4X5Z9GNfdGGCJ;pFDJ*_ z$<%Y3XLM%@IW)VX*MF91z+|Tq`;GT``dFx6G>9gY^<=V@r?p93LYahiCd9Xi_e<7+ zB2V42Vee1T0~WOp&4+$vb++RCVmc1nAUB^NS&?sOENp(pb+k`V&3iHr8(;*Z!12nb zMO5kpC`;(8DJAW1J#^=Ol0F^uoP(Wg*th9>($6U(&!3q`im3enT@=f{8vpvs@6mTA zTYo^5ggbNou4W$puv$ImD{YCVjM(>rmE#D~<}DdkUJ5tpvTCC12>sF{$#s0oFw-cHOoz(SY;=>`E}NyZ4`o}nyi^*xkAm-lS^ObddT>z|HW?djP*71 zk_=l_L96j7hcv;)xMd}n{A`3l$ZU2zBBG8nAtNilRP3fGI93UTqHGXzd5d7(Phd*A z5FrBTgG-hbI6`?c>TwFr*;_5h=js|zT+knf$72Mr6lH>Y4vN`SlZF>mPQ?)kB*?XK z0s_M=>C$$?nNu!czcKI3U(7CsNJ=Q*g=z_LTLhT?M07a4uPJIpkE{ zVqN)sN1|vz9FMOEju4dva>C)l;hF?w zEJVBa`E#ry%$H>jCEo;H5l*zb6dA~ghS+P!Kx@EMV+3-1Bo#i1=P*rb98?jxp?a@e zQyaDV=qfHLoL@dB`WB2|^I(~g&e*WD`D%8clOXTMctt}}jMsG;tD8c#pnu?V@M_1~ zw{92(gtXLAo9*-!W3qx#c@d2Nq}v$s(nZNd`ef&CEii@IQ{ULfV%IS)L(d7<=jv!z zR%CKMbKv9vdVFiQTfMsvhSO|$3E_75hnl7jhieB^dyDO(M~}_!u%sv3@ousGQ6Y_L zGp#1NVMPGH!TK~oKtQNPXd~iv5^*Giqx|@qi0W!wt5E%x^aoor>Q<=BxRxi<*$VvYcq{ zr@{w|vOC69tmi9nX@cc=@dH<5cu9)kb3M$Ckpr>b3tOp)_L(Q)?lTSNiB`uI=lf_z z)VyiF8ZjU0S|Oxz<)&8yYps;DA>xM89a10}phPhXFIrqEXp?|XJj%C%#qo&|BPXL! zkYnP3z@K~y2W!3{VUvh-i=Z=M0Tp#&+a!Q_PZ`vjSZsnn^wHRI7fMwrVKQtQ zVrn<+1Vzp^QC`G`NXji2*GEDvaVd+?q?_jkAKWopE4!=MAN@eyv~6U^a{Ew*Oa9N zn{Y!bbxBU%yTJ?PX8(*Ql^k5`s9~0yt_}1%5ZF3~9yfd%6S34)eaRB%ly&Lobt0QJ z-Hw(gN=~ktb?GXitDu7Hb~n84qrtJQ?{cKRcD{uktywl1Hx;-Pm=@MzfcaU3nxaP8)Jc?+F||wlU+v^@oo41M>Y_ zQw?VCoZKNUx(jq^PRMLt+h=loTWQt@X@=I}DoTuX?C0r}3B{G{W5-Kb({DLHxU(y8 zc9Hf8^et3L*uzMQ#63uTeCxISp<^2=O$;-L=ZqeXvInC5kUjPh9h$6!g5WEHPXq~Y zm{S~GI^Gc@EGKc!vO=@pRwdhTXTOhbeKZ4SY;m&`)>;wG|3=A=640L7-a7m+g)K9@N6YH+?Z~uFWhIB)p#;S85zmTKEt}*WtY{G zA$dAqI4Gg>uWBnB+EIo;Lfk)2~zA zNP2FJ>V?-CW;67a!rb^(LKDmR*w=S{O$wu&J53kWOq~yT9qOCoV4!LS`s*mj3$XS&&oNbXhI3mTuDRkx}D*w(#riRN&QSdK^%Pzn_9z=zH@~-HQBr zHxPTwSM8Z=bj4cAk1^nIVh$)g(c#{!Ae4-N`ROA-M!JqSMb89ih)-Tb?O z5waspS&HmmMqbq}D}rPegvj!puukW9D^IqNQ>4V;1M^_PzKSBD+=&U)ap0HqUpf!J z{Pwe5q$zFKu{(wUXqyTyo(GcpRY>X^l%rIc#qoGmh_rZY#={<9h39O9oUv zdogH9wIF`zj}H90Z}yS*ovZsVoAG?C)wDCr4Y0&YT?}8^ZGo-R9;B@fi(*342*whi za~4hpSlMTGU+;87Qw}cd@T!Y%!Y`}&`zlB4r{=H7C(U?!m=>3SrUj`C>fw2Eo}R2FZkk0f*knTXn7ec#qw<28@DT3H0%@satW zYIHowPgv)eE zivHJ=J*2$pAHKnNCA#$&Vr8X|L@v^|1NQ*+r#cHV{-3b98gv^I_DsZ6*Dt#8z3Hdv z@nccP?c7*4ZWC{BrrS{BlC6D?_qXCSTPMz6ODE4<7Y7O-kNK{OW21^HQ5|v%z>`Ta z7bEPTekq&J1DNw4Zgk4_%CN-R`jritw8v}yfS@~87mMFbHc!qfU&CId&6DIz?xwg% zc@`mjf7gt8j6j!kyJCZGkV%@@p;bKnk~b4$wLBj zx6yfb??t0`pfyPTo%ownGeyoMhy6r0ddZ=t&rbq!<{_a!=)k@g@4t=t4fq>49#BA99~ixy_=EvMxWv zIv57WD3CZSL4*lPORFLsx;d5?6~)~hU0b^cauT8`yxR}6C7X5Q*-clYWDO{lxX6O1 zIavz@`~wAj>}ZPI1M@wLk>qN;%ce)7q>jN4tAc9tup`R(JfdIC;9Et3-s<#7ycu-o zZ*N5lN1YTA=p)JMq~qG09yLA(S$D4Nq2{1gjok}clx`8(OF6Yxu3%qUfceUNe(Agg zcf4z2#M@iuywGYQ%PkJPFsYtl{roeTE7R*pRzW;|%%;+N4ajJIQ;xFfxVew+4@Kht z1DffJmTNHw(BRe_x0*|MF0@eZJ6+!tA+uB6`1Vw-V&R?!jpvd~QTkjZ!}?VYw*0Ml z%Nc>?8u#UE-=|a8=v$dkuQ9oM#LJ4TovaUMM(LG&#xPTt+qpy=^oM#r825oc!K*ae zOn#HbB-MtxfmDP8Lvt@v$Gx!nwl0T_kRLxgB54i#n6uazMI2mR<_VB0r6!8UaFlOz zOGZ=q4#|mGqpTrI*m)d5jjk=)z+t3oEX4<2#qpd_P%Du5$(!> z_bcG#u0-FrvTYd+J`R~2H^`ZBnT_0|fbVEAp=z1Pm~#rUC#>(n6qsSTHu*K+4c%5r zA?T*yJ-q~3&70L29%0Q`UQ2QnV#3x4Cz9VTC$hCES13Q=V}qZ@*S*cuG2e;E>3?u$ z1o;}b?tY8AHqX&QIFb`UL*sjXeN(XbBvA!nKo2|+-S%TdlY8-tm0C91wGSjnu7&|i z!*Kbaw9S8{$jsx(sDQ%zD%vL64*Ed5_^pXwh@Jsn;S*QpC}SI&&(UuB*BEa`z-fe| zG=R2>cNEN<4?>FAo0W1@5Zg@)87bIRs2cn|N=_769YIKvAf=w@{xs#uT+3|oN>1Oh zPsWTPgXwWjcjGC2q7?JH(W@7MQ}LqunE(@WWc_WQxXxf7FKi5AMKL&!Y~rHQZsQU= z!lsSIuK0RzJE~sW`kMqDn0;VKnijd9^{Van9J28|Ahq}X)VkBqOJwwWB7?lEZpoaw z%Y%)FS|82YVSWD$TdFp9{|w|kA7S)P3`` zJhPw+ea<|Z{a}`1{;H*J+;Y#fk@SFq%i<&9X{cJ) z`u1kvd?5&~00kv_C~oTMlh>ZsCFZLW{b|g?koIJ}diw_z%Meofl7%DAx0q8w*fye8 z!LfmIe0vIa(xjxtC}Xw+v*<3MR#9fFe0#=f~=agLodRy{A|LhepiS- zGyPyE#zhPdvk+k*hf!EHPF7X9z8sNbw%)F9q#emLeN8OnR%%*{oZ(1TkL41-T4G)O z(g7||cqTstmS&}(a3QTK-xMv!9=OCUj*lVWR_6WX6&FelKex*;>2^e6$skoyZmJm2 zmR4J!(Tx6i(V1QANMvilNuhJfvSFfvn=#MosOlFBhd>OKgT6&U9raQ!MuCr7_=50@ z!fu3n6&(&Z;}^dn)7Zv&8Ed&ZCKglf} zEf*{)vC;-bo&)*TkTe#q z^sUANp3nw)=gapOs>mNjwQ@AtG+%WziShgb#rzw_$Q@f;a}*m+q#ECHZd_^kW|ln4S)(oA}*e z#oUn4z!IBI%4s)dTYk#fUgR{zW;95LKiU`ed=X{PF}3Mgd6)ML#oPR_9!97Nw%m_S zq4j1GQhrET{I~)rKe&&P+BBztErOSmfpRzHbN|%A`)d9s`eI(9?LVOEug~{{n)jZ* z0f+-iE&&S#^OH=Y?1+4jvYqI+5e=nQT1mo0Q{3e#WP*Zo=0W1Z*N`@U-D<1T%2q0N zM={!Lr3S`e7SmI%Ph8zXEVp>M5I$~3CS>*z?a;}s43#B94^B{i>1E;Ci)hDK844wD zRYwTtI#7v@2q(YhzQkgNjRiaRKGUki(UOxN@mJ@RgImmad7gcZ;SmsGY+`F$x}P>P zuDMGi*v0K$^l?(}v4YuNjXmTA=GR3D#UGj?H~ayiu`8#>4@A1;f6r(RK8-|sDW%3Q zBMS@@((EH}VL#8uQ^b({Hn9`~p@mQ5LWLru`}#LCJ4ZnnU@=0cC>47K(3f1g5Le)@&PxTl2fv!Ix0=Or~L?Yau_z{O&Yk z5L>E6XR~4F)%0eh>JfOEpLTxtW%r_vXl0>}jNq~Q$U7m@V#a~VBmb2n1B-v2r6&#& zW%P`*!b^3IUjOI zdLzA&t`MF`(rurY=MZ|&DU!m~oNx)^727jd0hADmmmwKGtEg*9KS-WlHmzU^M_(`vEfwd0{<_G#s#@Y~{^>jJDhku3%P zP(w140#9#5{y|{$#h$?x8>5d+CeSuFe=+||VkH!uFBvUvy>?pO&Kut*5gzFj+Rr@B zF2(p@liBz8px@8Lw|*~H^834Pu!EJ1LJcV;nyq3ii;>$NRU-@+fqSYPdfh*ApPqIJ zQ#H|1wqM*&7jKM(or>RnnvtcD^6zhJWmjI(IHnxJ&ixr3md5c1G)&I{;hd~k@TC9Q zf6N^IF=*ghi_)jPDkwpMz?99%7u6sS8PyDz*$9S;Z{q*yq$pq2Ub5kd(_Iyf@7ltM z;=t6-3*Zm`L~$#Hf=v`?RUtr*A3a;@nf%j`@~Z*|v_Q8YeS(+Cc#E%Uvi4D??J@@d z(7IvKl_1A)Pd(B1nto&p*?_jGpKabmJ9uL$9YuQLsMzm6@nDPV_WQ*L5Bii>sSLPGX zth185kg3z7_pIkBafk%+^Uv=Cd*^e~k|3|^4xpNkRaA=oD7W$M{QNyK9DhZ5G_t2X zv(FdPN!nE@Q8j7)svaYN)Rc(Kh^&QAw^!_;ep|e&TP!gt5=nDXHo~0p3(+ z#Xov}pbp}M|K@2!KnB6{Q+;!d`2#AAewuhJy@b=8Q>v>oXiSO`WpBDs4cR)a@L7ZR z)KY1Fkem%PIlplWXZzIL=c=KC!tn#!_f_iB#sjsoQNkRr)A|@Xv~*RJOV0@{1T`lE zBg&C^vOKmGn3sHzt-^r+w&D?U!=gPzDfVU3N8yVete>qZk1sZ?l0SM=qy>(`PZQ}$ zUXng6m6zsNVEzbthr?-o8#{+GqP!(7cJ~zyUSj59k+lz?+@P;=BNM zdtaK3rLK4Ptnmobs6nvvvh369M>-TT%#`jiH1B7($w|u40HJwL+MgqiJM|$bP$KG<V(MfDQ1&62&skIX39Z500cNBxFYUAEY_jGDVUN)yAWGRnaRg1Ta{8s-1 zYPL;!O@wIi8}zuB%lSs}lq%FnUBi!(k(T{C4vf~_kJ;my5cXLvQ<{QDO)7^ldzFe;8lSK6rn%hIDIrc)&Pb@#_S%!4ACmAr=SzXWPX!PHCXc^zJMY zGqhHU3e%?ym`J9zH6`k4{Q+lZn2lUxcZ+OSxepYfWly)2(H6R3#0Q2eNhoIgEdlQ@^rNuQJ1xqMw$&Lb`(VT&Xf7)srZAd z%nRJNq;!7FWwYnpwjBAv#p_dO*su*$vcZBOCqB#RcFwjUM1XtV zztxm~#O2I|4B=^FtP-AP!k0CN`>5-=zX6o9Uge)@_vW*ZP*R;9TeQWi*rvDVVc>(= zyz#;3@eJI+ok;BdRy1UD?OO+_sn@a}U4OG~E3Ef70~zp=&Hs^d_)psH|Es?h(RgOo z7-BH**$y+$_-8T9;{#FhGI_SCIOfvR1=gaBjK}^K4zyC*4!2J)x0F9T&(I-{5U47^ z18xn=sMp?fI*<0*YnC$CQBNs@;hAjVj1L&<@)<1BtsRfOEG&wKtR!U(_uKIgsN`Qp zeZYUZ_#yvJeJ|iInNo|w75ZRulf{d6XdB)+cHMD8)yn`sk{(;C7dtj$;NiV_x$&G1 z;(xeW^!OJ)FSeDckd@YN>M{pQe2q#Z*S!)#7w;=2@Y+nbhW)`F=pLUOzqsDxCEpOK zWOox_V#tg62|O#>+#k?6b~C`)w!G6~+Ty7#=|9TZo>MOYlC7}kmh2t<62S9WeUB55 z08LN85r5DtiE2gbnh<;VKsxY3ZyHn&5FiP&DzcYldT0)7+X?|Jtm1za>{_dYq9Fc!s*w+vm zh_?f?wk<)r&dJ@t>{e&?PewBPC%tRF0NK3G)!7$>6t>QR*Jw?WYJp|FfR6~P(q1V< zJP)HU;&i6gd-Y$TSzQZnazj<8KxRoBIr1aYZk_ijPsQUnIY z+nHBfAzhR=MN%0|j&JrO(=BA__@E~h<{s`+eJJ-XRp#P<*8}WX@rq1{#6~ZpvgEgO z`vD9wA-+(A#b3V5?TAB1c3d7`b(^q>{VL;MT+$e}$BS62Cv}`TXT{Rk1$J){%)E~d zKiA!VMk-5u9_6+k?_F^E%(To!GFJ|)g754^&>G7V|82KF_B^kd*v$uMdR;SqJR))4aVa0r~wn!aHb>p z)5ygsA?Z67OjmL(X;9NmWNZG_{rmah&#uU2`LjdkkQF!cO#3fX3rOwq`p2`SBAtWi z)p+M21cwLTSupm3)rpG+ST+vVf^5W6M>7+qe;<0w(LMj5hNJZ{r&JbpK~*U_1))F- z#0FfE?w@dSap0Yl@3I}<$D-o_C*b@$o(ulrBH7Q%NqvuNZ&2Q!M^gA8a>7l=7y{0o zX}a<`GZQvvyap*ayU2R-t96F91y|qBKBd#5g=l8-u;RA#SlAImu0^|*1t~k(R_iZ~ z*ha>BHuvRvLQv=KhNTOP_2=V-Wx;!e)hq%D*hGo@=!nva7Y^}L_>y{PdQD<_;^Bb8 z?xYZnm>ab&PRD858rWZWTld!}Pafw2ebMp653@KK7^|W5-GG13oy`US_plN@3ryeM z#{&rlRhDM~%=7}(v#F7#iSWju%4PfYwfl@3$@5vD6Exd`*_<6#kT021I)xE4>y!>4 z)fQ?T?TMi!p`KXNQ6N`DfiDy#U6}m%EvV3b4W+vid6iDxcD8`M2u{5|YWF)fu|VY4 zUUQ>%MdbQax@k(+MaBr7Iz5|Q_Np$J4A=2lW(T_riBRu+Gh$$#V@wc9bJmlN$fcHf z>TVJdz3-zL-N<=cn>h9(YHv?06$KuEKU33&l0}=`!3J4r;-dde25J+ug?GyTdMh%5 zP20hCB+7q*zDD;_k4l@tt1_A%A-jIFAy`Ye5svjK&T2HEa~Uv(y+$270#))B2HO*Z{Q?flyMd(a2pFQ! z_{t|D{zP%NwcM5aYLBuXk=aY|S&Tz-M!3xUvp}We5-rqMCV4V#5A$>uO?J4;Dx}_x zzT(J}BnqC?D<#=J2^N&e3Zv(Hw0Pqlc}XG>we@6LVaYaTNMXfV#2*U6&$_y}g@lE8 zR$4*4{W9^I4F9!r>G5^PnjsC2m{7xjm7-b>VgEsj%#lncbVz1p!Zq3l>?OuEfDsa5 z8ZUjti)+^4j%hmD<+Q^1@Q}-o{Q&_$^6V~{1rb-Pl2wdvq|FGTaY^srGfn@c$_Xrp zLxe5J-!|#uY#0I`FfYm$m+m0kj0d;seKfhVF%{5nEYDuCEYt9rtSR52Np(t&FF}b~ zu{+F)`Xe!_5L3!C>GGK{qkAu4hCv;G!^O$Qun1d40Ed?oo z>c9b>1`0rg)r?Sxn=O`z=zou% z;LC6Nrg#}0-~%?Lh@_AC4jo$I!*ou|S@kTS0?Tl~EUp=8(O z=0W<^BDa2Y)GLGmInOs;@bsm3a!Vk_-lUm)pjH=K8va)P+Ba3IW!1H1tzCwL$(d)# zcib18x&1MWvU&>8Vij>j?CQCbm6srTpO|4cdT4YqP1OqaLI$Lc?Fd_STMuBQ{yA!` z-%8_%^QP4joeGs_f;-+{EqhGh{EH1xWSd~;fwYDiD(kzDQo=*M$!_%b+};zH_IQiX z8}+17H763o9XtuKVJV*$)(?HPLZ8G-4varD=lVn-eeBZPJe@6AsCS#nj5;6{hZtT(4pQU`X*y9LeuK~x^kfY)GpcX25?*e|RrSo)3LPU^AKN;llSYLBbz zey;~Wsw!V+KXF`o#PTc?>`s@=p&%#a{7LQpoKQ6nB=UQ2ztvdf^3fF$G9w^uLNC4$ zkhhrXgr)VNX=VV_daTer{H~JufvKNOO?$fq&sT}ht6VHSe)xfVQ0CL=o%XkXPyPGr z+ueWSS4E`>`JLBet-lg~vy_{qn?KV8sBP9(LmZ{3sFRIx-etk&aNdZi`Sx9-i* z&!sP!8rA{~>uBVrc$}>TkDbNv`@!1^2Kh%mZb1W<7`*vjY*nlLJfPy-L7!YvxnS}8 zB*)lYSTzvRb}1(8PaL~+HU7@SC0s255e2iXo`eNLXr{I5uMgN`{bC0DD2t4N@5RmA ztmDu%hITFpt-UJ(BN_=ps~~X1#mVV4R$g)8xb{7SKGb-MkAIT==?%@-xyh?AiF6~> zjJtnZ>#2F`TX+CJM>kK|WT1M3A*(^*S zSn&hmutd?epD0I`gGsZkx|t>YL6vE9t*Sr2N#|D=S1-*7S-n<2oALNyGFu1giVNp$ zFhtYy;z@Mg%da8w=XQb?w^}}YJ?|oX`4#d(HhLvLzWIw`Zu}T;H0I7h)$6|M;?C8C zhx71`gxAI*G@vnKIaofy__xEB|bFAL6k#mn!LL)&Sy2ETA`qIE)T6wAayta$vS>29lW zS7(knuRC)vjyHLLyvX?IjbVZmXew~ zzu8^LYR><&!7@zc_J-_=YFQ;T_E{B>8WDLmeCeDrP&y}b+v(jX$SYMPR`USq%agiG ztqH@=t!2fnuYfDKPZUTiew)SlJb8_v+*9{mc1*c!+ok_bHIrDQX3U$%oP@!w)8FBr z6xh!n5SWeAP3r0OGLWuXlDBO!1E6G>OZ57Z+^i%uV8G1OIP1>*{^9_Nc^zN~uP;R%Oh4EjWbc{}V3pG@>R~$a$5|_|=0Fy=1d?lQjr{k%} zw#+IYQP8pTyBii$W3ug}g)Pdwze}@`=B86kPfxxzr7OZz^lg+52YVTdm7CdEi`zig z`FvS(n8d`|cz?>a`Z-DcC$3=4LG0lxuMP-$R`$9sZ0y;UyQxK+@lXa6PYbcsv9z9P zy?+1?VuEq5Llt|J?)r@iM`}(f$ELnjpL5|wY6RGsw>?&E<5?385jJWg>4pZwQ3rq8 zUgzCR)D>S|nM}V}bL;d@^&Tq+B6SKTgd!Pe#pPSWVqzwCyzZB_+m-q<(nqlS6{CvJDqM zMBY6VYj>dR+1YvkF_gO^QhcCHZr*R&tH{bAcbY~a7v1}b*)6Ej}_c>e!n IK-?PqCnwi6zyJUM literal 0 HcmV?d00001 diff --git a/docs/source/Plugin/_Plugin.rst b/docs/source/Plugin/_Plugin.rst index ed87fae27e..0e79c91ed5 100644 --- a/docs/source/Plugin/_Plugin.rst +++ b/docs/source/Plugin/_Plugin.rst @@ -386,7 +386,7 @@ There are different released versions of ESP Easy: ":ref:`P153_page`","|P153_status|","P153" ":ref:`P154_page`","|P154_status|","P154" ":ref:`P159_page`","|P159_status|","P159" - + ":ref:`P164_page`","|P164_status|","P164" Internal GPIO handling ---------------------- diff --git a/docs/source/Plugin/_plugin_categories.repl b/docs/source/Plugin/_plugin_categories.repl index e9b11a450e..9347076fd7 100644 --- a/docs/source/Plugin/_plugin_categories.repl +++ b/docs/source/Plugin/_plugin_categories.repl @@ -10,7 +10,7 @@ .. |Plugin_Energy_Heat| replace:: :ref:`P088_page`, :ref:`P093_page` .. |Plugin_Environment| replace:: :ref:`P004_page`, :ref:`P005_page`, :ref:`P006_page`, :ref:`P014_page`, :ref:`P024_page`, :ref:`P028_page`, :ref:`P030_page`, :ref:`P031_page`, :ref:`P032_page`, :ref:`P034_page`, :ref:`P039_page`, :ref:`P047_page`, :ref:`P051_page`, :ref:`P068_page`, :ref:`P069_page`, :ref:`P072_page`, :ref:`P103_page`, :ref:`P105_page`, :ref:`P106_page`, :ref:`P122_page`, :ref:`P150_page`, :ref:`P151_page`, :ref:`P153_page`, :ref:`P154_page` .. |Plugin_Extra_IO| replace:: :ref:`P011_page`, :ref:`P022_page` -.. |Plugin_Gases| replace:: :ref:`P049_page`, :ref:`P052_page`, :ref:`P083_page`, :ref:`P090_page`, :ref:`P117_page`, :ref:`P127_page`, :ref:`P135_page`, :ref:`P145_page`, :ref:`P147_page` +.. |Plugin_Gases| replace:: :ref:`P049_page`, :ref:`P052_page`, :ref:`P083_page`, :ref:`P090_page`, :ref:`P117_page`, :ref:`P127_page`, :ref:`P135_page`, :ref:`P145_page`, :ref:`P147_page`, :ref:`P164_page` .. |Plugin_Generic| replace:: :ref:`P003_page`, :ref:`P026_page`, :ref:`P033_page`, :ref:`P037_page`, :ref:`P081_page`, :ref:`P100_page`, :ref:`P146_page` .. |Plugin_Gesture| replace:: :ref:`P064_page` .. |Plugin_Gyro| replace:: :ref:`P045_page`, :ref:`P119_page` diff --git a/docs/source/Plugin/_plugin_substitutions.repl b/docs/source/Plugin/_plugin_substitutions.repl index f6b9bf1f3e..8a86c4cf80 100644 --- a/docs/source/Plugin/_plugin_substitutions.repl +++ b/docs/source/Plugin/_plugin_substitutions.repl @@ -14,3 +14,4 @@ .. include:: ../Plugin/_plugin_substitutions_p13x.repl .. include:: ../Plugin/_plugin_substitutions_p14x.repl .. include:: ../Plugin/_plugin_substitutions_p15x.repl +.. include:: ../Plugin/_plugin_substitutions_p16x.repl diff --git a/docs/source/Plugin/_plugin_substitutions_p16x.repl b/docs/source/Plugin/_plugin_substitutions_p16x.repl new file mode 100644 index 0000000000..b6ef18ce4e --- /dev/null +++ b/docs/source/Plugin/_plugin_substitutions_p16x.repl @@ -0,0 +1,13 @@ +.. |P164_name| replace:: :cyan:`ENS16x TVOC/eCO2` +.. |P164_type| replace:: :cyan:`Gases` +.. |P164_typename| replace:: :cyan:`Gases - ENS16x TVOC/eCO2` +.. |P164_porttype| replace:: `.` +.. |P164_status| replace:: :yellow:`COLLECTION G` :yellow:`CLIMATE` +.. |P164_github| replace:: P164_gases_ens160.ino +.. _P164_github: https://github.com/letscontrolit/ESPEasy/blob/mega/src/_P164_gases_ens160.ino +.. |P164_usedby| replace:: `.` +.. |P164_shortinfo| replace:: `ENS16x TVOC/eCO2 sensors` +.. |P164_maintainer| replace:: `flashmark` +.. |P164_compileinfo| replace:: `.` +.. |P164_usedlibraries| replace:: `I2C` + diff --git a/src/_P164_gases_ens160.ino b/src/_P164_gases_ens160.ino index 63bdbc65b2..d7ca63f143 100644 --- a/src/_P164_gases_ens160.ino +++ b/src/_P164_gases_ens160.ino @@ -6,10 +6,10 @@ // ####################################################################################################### // P164 "GASES - ENS16x (TVOC, eCO2)" // Plugin for ENS160 & ENS161 TVOC and eCO2 sensor with I2C interface from ScioSense -// Based upon: https://github.com/sciosense/ENS160_driver -// For documentation see +// For documentation of the ENS160 hardware device see // https://www.sciosense.com/wp-content/uploads/documents/SC-001224-DS-9-ENS160-Datasheet.pdf // +// PLugin code: // 2023 By flashmark // ####################################################################################################### @@ -65,7 +65,7 @@ boolean Plugin_164(uint8_t function, struct EventStruct *event, String& string) constexpr int nrAddressOptions = NR_ELEMENTS(i2cAddressValues); if (function == PLUGIN_WEBFORM_SHOW_I2C_PARAMS) { - addFormSelectorI2C(F("i2c_addr"), nrAddressOptions, i2cAddressValues, P164_I2C_ADDR); + addFormSelectorI2C(F("i2c_addr"), nrAddressOptions, i2cAddressValues, P164_PCONFIG_I2C_ADDR); addFormNote(F("ADDR Low=0x52, High=0x53")); } else { success = intArrayContains(nrAddressOptions, i2cAddressValues, event->Par1); @@ -77,7 +77,7 @@ boolean Plugin_164(uint8_t function, struct EventStruct *event, String& string) # if FEATURE_I2C_GET_ADDRESS case PLUGIN_I2C_GET_ADDRESS: { - event->Par1 = P164_I2C_ADDR; + event->Par1 = P164_PCONFIG_I2C_ADDR; success = true; break; } @@ -85,7 +85,7 @@ boolean Plugin_164(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_SET_DEFAULTS: { - P164_I2C_ADDR = ENS160_I2CADDR_1; + P164_PCONFIG_I2C_ADDR = ENS160_I2CADDR_1; success = true; break; } @@ -104,7 +104,7 @@ boolean Plugin_164(uint8_t function, struct EventStruct *event, String& string) P164_data_struct *P164_data = static_cast(getPluginTaskData(event->TaskIndex)); if (nullptr == P164_data) { - Serial.print("P164: plugin_read NULLPTR"); + addLogMove(LOG_LEVEL_ERROR, "P164: plugin_read NULLPTR"); break; } @@ -113,12 +113,10 @@ boolean Plugin_164(uint8_t function, struct EventStruct *event, String& string) if (validTaskIndex(P164_PCONFIG_TEMP_TASK) && validTaskIndex(P164_PCONFIG_HUM_TASK)) { // we're checking a var from another task, so calculate that basevar - temperature = UserVar[P164_PCONFIG_TEMP_TASK * VARS_PER_TASK + P164_PCONFIG_TEMP_VAL]; // in degrees C - humidity = UserVar[P164_PCONFIG_HUM_TASK * VARS_PER_TASK + P164_PCONFIG_HUM_VAL]; // in % relative + temperature = UserVar.getFloat(P164_PCONFIG_TEMP_TASK, P164_PCONFIG_TEMP_VAL); // in degrees C + humidity = UserVar.getFloat(P164_PCONFIG_HUM_TASK, P164_PCONFIG_HUM_VAL); // in % relative } success = P164_data->read(UserVar[event->BaseVarIndex], UserVar[event->BaseVarIndex + 1], temperature, humidity); - - success = true; break; } @@ -142,15 +140,8 @@ boolean Plugin_164(uint8_t function, struct EventStruct *event, String& string) break; } success = P164_data->tenPerSecond(event); - success = true; - } - - case PLUGIN_FIFTY_PER_SECOND: - case PLUGIN_ONCE_A_SECOND: - { break; } - } return success; } diff --git a/src/src/PluginStructs/P164_data_struct.cpp b/src/src/PluginStructs/P164_data_struct.cpp index d93db8dca1..2d9debd0ef 100644 --- a/src/src/PluginStructs/P164_data_struct.cpp +++ b/src/src/PluginStructs/P164_data_struct.cpp @@ -5,6 +5,17 @@ // For documentation see // https://www.sciosense.com/wp-content/uploads/documents/SC-001224-DS-9-ENS160-Datasheet.pdf // +// Based upon: https://github.com/sciosense/ENS160_driver +// MIT License for the original code referenced above +// Copyright (c) 2020 Sciosense +// 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. +// // 2023 By flashmark /////////////////////////////////////////////////////////////////////////////////////////////////// @@ -26,7 +37,8 @@ #define ENS160_REG_DATA_AQI 0x21 // Air quality according to index according to UBA #define ENS160_REG_DATA_TVOC 0x22 // Equivalent TVOC concentartion (ppb) #define ENS160_REG_DATA_ECO2 0x24 // Equivalent CO2 concentration (ppm) -#define ENS160_REG_DATA_BL 0x28 // Relative air quality index according to ScioSense +#define ENS160_REG_DATA_AQI_S 0x26 // Relative Air Quality Index according to ScioSense [ENS161 only] +#define ENS160_REG_DATA_BL 0x28 // Undocumented #define ENS160_REG_DATA_T 0x30 // Temperature used in calculations #define ENS160_REG_DATA_RH 0x32 // Relative humidity used in calculations #define ENS160_REG_DATA_MISR 0x38 //Data integrity field (optional) @@ -39,7 +51,11 @@ #define ENS160_REG_GPR_WRITE_6 ENS160_REG_GPR_WRITE_0 + 6 #define ENS160_REG_GPR_WRITE_7 ENS160_REG_GPR_WRITE_0 + 7 #define ENS160_REG_GPR_READ_0 0x48 // General purpose read registers [8 bytes] +#define ENS160_REG_GPR_READ_1 ENS160_REG_GPR_READ_0 + 1 +#define ENS160_REG_GPR_READ_2 ENS160_REG_GPR_READ_0 + 2 +#define ENS160_REG_GPR_READ_3 ENS160_REG_GPR_READ_0 + 3 #define ENS160_REG_GPR_READ_4 ENS160_REG_GPR_READ_0 + 4 +#define ENS160_REG_GPR_READ_5 ENS160_REG_GPR_READ_0 + 5 #define ENS160_REG_GPR_READ_6 ENS160_REG_GPR_READ_0 + 6 #define ENS160_REG_GPR_READ_7 ENS160_REG_GPR_READ_0 + 7 @@ -98,23 +114,18 @@ #define CONVERT_RS_RAW2OHMS_F(x) (pow (2, (float)(x) / 2048)) // Form IDs used on the device setup page. Should be a short unique string. -#define P164_GUID_TEMP_T "f08" -#define P164_GUID_TEMP_V "f09" -#define P164_GUID_HUM_T "f10" -#define P164_GUID_HUM_V "f11" +#define P164_GUID_TEMP_T "f01" +#define P164_GUID_TEMP_V "f02" +#define P164_GUID_HUM_T "f03" +#define P164_GUID_HUM_V "f04" /////////////////////////////////////////////////////////////////////////////////////////////////// // Constructor // /////////////////////////////////////////////////////////////////////////////////////////////////// P164_data_struct::P164_data_struct(struct EventStruct *event) : - i2cAddress(P164_I2C_ADDR) + i2cAddress(P164_PCONFIG_I2C_ADDR) { - #ifdef P164_ENS160_DEBUG - Serial.println(F("ENS160: Constructor")); - Serial.print("ENS160: I2C address ="); - Serial.println(i2cAddress, HEX); - #endif } /////////////////////////////////////////////////////////////////////////////////////////////////// @@ -122,13 +133,15 @@ P164_data_struct::P164_data_struct(struct EventStruct *event) : /////////////////////////////////////////////////////////////////////////////////////////////////// bool P164_data_struct::begin() { + // Start connecting the device over I2C if (!start(i2cAddress)) { - Serial.println(F("P164: device initialization ***FAILED***")); + addLogMove(LOG_LEVEL_ERROR, F("P164: device initialization FAILED")); return false; } - setMode(ENS160_OPMODE_STD); + setMode(ENS160_OPMODE_STD); // For now we only support the standard acquisition mode + #ifdef P164_ENS160_DEBUG - Serial.println(F("P164: begin(): success")); + addLogMove(LOG_LEVEL_DEBUG, F("P164: begin(): success")); #endif return true; } @@ -138,9 +151,9 @@ bool P164_data_struct::begin() /////////////////////////////////////////////////////////////////////////////////////////////////// bool P164_data_struct::read(float& tvoc, float& eco2) { - bool success = measure(); - tvoc = (float)_data_tvoc; - eco2 = (float)_data_eco2; + bool success = measure(); // Read measurement values from device + tvoc = (float)_data_tvoc; // Latest acquired TVOC value + eco2 = (float)_data_eco2; // Latest aquired eCO2 value return success; } @@ -150,7 +163,7 @@ bool P164_data_struct::read(float& tvoc, float& eco2) bool P164_data_struct::read(float& tvoc, float& eco2, float temp, float hum) { this->set_envdata(temp, hum); // Write new compensation temp & hum to device - bool success = measure(); // Read emasurement values from device + bool success = measure(); // Read measurement values from device tvoc = (float)_data_tvoc; // Latest acquired TVOC value eco2 = (float)_data_eco2; // Latest aquired eCO2 value return success; @@ -166,9 +179,7 @@ bool P164_data_struct::webformLoad(struct EventStruct *event) uint16_t chipID = 0; addRowLabel(F("Detected Sensor Type")); - - chipID = I2C_read16_LE_reg(P164_I2C_ADDR, ENS160_REG_PART_ID, &found); - + chipID = I2C_read16_LE_reg(P164_PCONFIG_I2C_ADDR, ENS160_REG_PART_ID, &found); if (!found) { addHtml(F("No device found")); } else if (chipID == ENS160_PARTID) { @@ -179,6 +190,16 @@ bool P164_data_struct::webformLoad(struct EventStruct *event) addHtmlInt(chipID); } + P164_data_struct *P164_data = static_cast(getPluginTaskData(event->TaskIndex)); + if (P164_data != nullptr) { + addHtml(F(" Firmware: ")); + addHtmlInt(P164_data->getMajorRev()); + addHtml(F(".")); + addHtmlInt(P164_data->getMinorRev()); + addHtml(F(".")); + addHtmlInt(P164_data->getBuild()); + } + addFormNote(F("Both Temperature and Humidity task & values are needed to enable compensation")); // temperature addRowLabel(F("Temperature Task")); @@ -209,8 +230,7 @@ bool P164_data_struct::webformLoad(struct EventStruct *event) /////////////////////////////////////////////////////////////////////////////////////////////////// bool P164_data_struct::webformSave(struct EventStruct *event) { - P164_I2C_ADDR = getFormItemInt(F("i2c_addr")); - + P164_PCONFIG_I2C_ADDR = getFormItemInt(F("i2c_addr")); P164_PCONFIG_TEMP_TASK = getFormItemInt(F(P164_GUID_TEMP_T)); P164_PCONFIG_TEMP_VAL = getFormItemInt(F(P164_GUID_TEMP_V)); P164_PCONFIG_HUM_TASK = getFormItemInt(F(P164_GUID_HUM_T)); @@ -226,30 +246,56 @@ bool P164_data_struct::tenPerSecond(struct EventStruct *event) return evaluateState(); } +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Set operation mode of sensor // +// Note: This function is to set the operation mode to the plugin software structure only // +// The statemachine shall handle the actual programming of the OPMODE register // +/////////////////////////////////////////////////////////////////////////////////////////////////// +bool P164_data_struct::setMode(uint8_t mode) { + bool result = false; + + // LP only valid for rev>0 + if (!(mode == ENS160_OPMODE_LP) and (_revENS16x == 0)) { + this->_opmode = mode; + result = true; + } + + #ifdef P164_ENS160_DEBUG + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + String log; + log += F("P164: setMode("); + log += mode; + log += F(")"); + addLogMove(LOG_LEVEL_DEBUG, log); + } + #endif // ifdef P164_ENS160_DEBUG + + return result; +} + /////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////// // **** The sensor handling code **** // // This is based upon Sciosense code on github // -// The code is adapted to fit the ESPEasy structures // +// The code is adapted to fit the ESPEasy structures and statemachine behavior is added // /////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////// // Helper function to display the current state in readable format // /////////////////////////////////////////////////////////////////////////////////////////////////// #ifdef P164_ENS160_DEBUG -void printState(int state) { - #ifdef P164_ENS160_DEBUG - +String printState(int state) { switch (state) { - case P164_STATE_INITIAL: Serial.print(F("initial")); break; - case P164_STATE_ERROR: Serial.print(F("error")); break; - case P164_STATE_RESETTING: Serial.print(F("resetting")); break; - case P164_STATE_IDLE: Serial.print(F("idle")); break; - case P164_STATE_DEEPSLEEP: Serial.print(F("deepsleep")); break; - case P164_STATE_OPERATIONAL: Serial.print(F("operational")); break; - default: Serial.print(F("***ERROR***")); break; + case P164_STATE_INITIAL: return F("initial"); break; + case P164_STATE_ERROR: return F("error"); break; + case P164_STATE_RESETTING: return F("resetting"); break; + case P164_STATE_STARTING1: return F("starting1"); break; + case P164_STATE_STARTING2: return F("starting2"); break; + case P164_STATE_IDLE: return F("idle"); break; + case P164_STATE_DEEPSLEEP: return F("deepsleep"); break; + case P164_STATE_OPERATIONAL: return F("operational"); break; + default: return F("***ERROR***"); break; } - #endif // ifdef P164_ENS160_DEBUG } #endif @@ -264,6 +310,8 @@ void printState(int state) { // + INITIAL Class is constructed, waiting for begin() // // + ERROR Communication with the device failed or other fatal error conditions // // + RESETTING Waiting for device reset to be finished // +// + STARTING1 Startup sequence waiting for previous command to be acknowledged // +// + STARTING2 Startup sequence waiting for previous command to be acknowledged // // Note that the ENS161 device has various gas sensing operation modes which all map to the // // same OPERATIONAL state. These are combined in the same OPMODE register in the device // // - STANDARD // @@ -293,15 +341,33 @@ bool P164_data_struct::evaluateState() } break; case P164_STATE_RESETTING: + // Device has been reset, give it some time to get accessible again this->_available = false; - // Once device has rebooted read some stuff from it and move to idle - if (timePassedSince(this->_lastChange) > ENS160_BOOTING) { // Wait until device has rebooted - this->checkPartID(); + // Once device has rebooted check if it is a supported device at the given I2C address + if (timePassedSince(this->_lastChange) > ENS160_BOOTING) { + if (this->checkPartID()) + { + newState = P164_STATE_STARTING1; + } + } + break; + case P164_STATE_STARTING1: + // A valid device is found, check if its status is ready to continue + this->_available = false; + this->getStatus(); + if (GET_STATUS_VALIDITY(this->_statusReg) == 0) + { this->writeMode(ENS160_OPMODE_IDLE); this->clearCommand(); + newState = P164_STATE_STARTING2; + } + break; + case P164_STATE_STARTING2: + this->_available = false; + this->getStatus(); + if (GET_STATUS_VALIDITY(this->_statusReg) == 0) { this->getFirmware(); - this->getStatus(); newState = P164_STATE_IDLE; } break; @@ -358,26 +424,19 @@ bool P164_data_struct::evaluateState() } if (newState != this->_state) { - this->_state = newState; - this->_lastChange = millis(); #ifdef P164_ENS160_DEBUG - Serial.print(F("P164: State transition->")); - printState(newState); - Serial.print(F("; opmode= ")); - Serial.print(this->_opmode); - Serial.println(F(".")); + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + String log = F("P164: State transition "); + log += printState(this->_state); + log += F(" -> "); + log += printState(newState); + log += F("; opmode= "); + log += this->_opmode; + addLogMove(LOG_LEVEL_DEBUG, log); + } #endif // ifdef P164_ENS160_DEBUG - } - else { - #ifdef P164_ENS160_DEBUG -// if (millis() > (this->_dbgtm + 1000)) { -// this->_dbgtm = millis(); -// Serial.print(F("P164: state ")); -// printState(newState); -// Serial.print(F(" opmode ")); -// Serial.println(this->_opmode); -// } - #endif + this->_state = newState; + this->_lastChange = millis(); } return success; @@ -385,9 +444,21 @@ bool P164_data_struct::evaluateState() /////////////////////////////////////////////////////////////////////////////////////////////////// // Helper function to enter a new state // +// Function must be called outside evaluateState() when an action causes a state transition // /////////////////////////////////////////////////////////////////////////////////////////////////// void P164_data_struct::moveToState(P164_state newState) { + #ifdef P164_ENS160_DEBUG + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + String log; + log += F("P164: Move to state: "); + log += printState(this->_state); + log += F(" --> "); + log += printState(newState); + addLogMove(LOG_LEVEL_DEBUG, log); + } + #endif // ifdef P164_ENS160_DEBUG + this->_state = newState; // Enter the new state this->_lastChange = millis(); // Mark time of transition this->evaluateState(); // Check if we can already move on @@ -395,7 +466,6 @@ void P164_data_struct::moveToState(P164_state newState) /////////////////////////////////////////////////////////////////////////////////////////////////// // Init the device: // -// Setup optional hardware pins (not implemented yet) // // Reset ENS16x // // Returns false on encountered errors // /////////////////////////////////////////////////////////////////////////////////////////////////// @@ -404,23 +474,22 @@ bool P164_data_struct::start(uint8_t slaveaddr) uint8_t result = 0; // Initialize internal bookkeeping; - this->_state = P164_STATE_INITIAL; // Assume nothing, start clean + this->_state = P164_STATE_INITIAL; // Assume nothing, start clean this->_lastChange = millis(); // Bookmark last state change as now this->_available = false; this->_opmode = ENS160_OPMODE_STD; - - // Set IO pin levels - // TODO: It is doubtable we will use the INT pin in future - if (this->_nINT > 0) { - pinMode(this->_nINT, INPUT_PULLUP); // INT is open drain output pin for the device - } + this->i2cAddress = slaveaddr; result = this->writeMode(ENS160_OPMODE_RESET); // Reset the device, takes some time - this->moveToState(P164_STATE_RESETTING); // Go to next state RESETTING + this->moveToState(P164_STATE_RESETTING); // Go to next state RESETTING #ifdef P164_ENS160_DEBUG - Serial.print(F("P164: reset() result: ")); - Serial.println(result == 0 ? F("ok") : F("nok")); + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + String log; + log += F("P164: start() result= "); + log += result ? F("ok") : F("nok"); + addLogMove(LOG_LEVEL_DEBUG, log); + } #endif return result; } @@ -431,28 +500,18 @@ bool P164_data_struct::start(uint8_t slaveaddr) /////////////////////////////////////////////////////////////////////////////////////////////////// bool P164_data_struct::measure() { uint8_t i2cbuf[8]; - uint8_t status; bool newData = false; - - #ifdef P164_ENS160_DEBUG - Serial.println(F("P164: Start measurement")); - #endif // ifdef P164_ENS160_DEBUG + bool is_ok; if (this->_state == P164_STATE_OPERATIONAL) { - // Check if new data is aquired - if (this->getStatus()) { - status = this->_statusReg; - - // Read predictions - if (IS_NEWDAT(status)) { + if (this->getStatus()) { // Check status register if new data is aquired + if (IS_NEWDAT(this->_statusReg)) { newData = true; - P164_data_struct::read(i2cAddress, ENS160_REG_DATA_AQI, i2cbuf, 7); - _data_aqi = i2cbuf[0]; - _data_tvoc = i2cbuf[1] | ((uint16_t)i2cbuf[2] << 8); - _data_eco2 = i2cbuf[3] | ((uint16_t)i2cbuf[4] << 8); - - if (_revENS16x > 0) { - _data_aqi500 = ((uint16_t)i2cbuf[5]) | ((uint16_t)i2cbuf[6] << 8); + _data_aqi = I2C_read8_reg(i2cAddress, ENS160_REG_DATA_AQI, &is_ok); + _data_tvoc = I2C_read16_LE_reg(i2cAddress, ENS160_REG_DATA_TVOC, &is_ok); + _data_eco2 = I2C_read16_LE_reg(i2cAddress, ENS160_REG_DATA_ECO2, &is_ok); + if (_revENS16x > 0) { // AQI500 only available for ENS161 + _data_aqi500 = I2C_read16_LE_reg(i2cAddress, ENS160_REG_DATA_AQI_S, &is_ok); } else { _data_aqi500 = 0; @@ -466,14 +525,22 @@ bool P164_data_struct::measure() { } #ifdef P164_ENS160_DEBUG - Serial.print(F("P164: measure: aqi= ")); - Serial.print(_data_aqi); - Serial.print(F(" tvoc= ")); - Serial.print(_data_tvoc); - Serial.print(F(" eco2= ")); - Serial.print(_data_eco2); - Serial.print(F(" newdata = ")); - Serial.println(newData); + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + String log; + log += F("P164: measure() state= "); + log += printState(this->_state); + log += F(", aqi= "); + log += _data_aqi; + log += F(", tvoc= "); + log += _data_tvoc; + log += F(", eco2= "); + log += _data_eco2; + log += F(", AQI500= "); + log += _data_aqi500; + log += F(", newdata = "); + log += newData; + addLogMove(LOG_LEVEL_INFO, log); + } #endif // ifdef P164_ENS160_DEBUG return newData; @@ -490,18 +557,19 @@ bool P164_data_struct::measure() { // This feature is not documented in the datasheet, but code is provided by Sciosense // /////////////////////////////////////////////////////////////////////////////////////////////////// bool P164_data_struct::initCustomMode(uint16_t stepNum) { - uint8_t result; + bool result = false; if (stepNum > 0) { this->_stepCount = stepNum; result = this->writeMode(ENS160_OPMODE_IDLE); result = this->clearCommand(); - result = P164_data_struct::write8(i2cAddress, ENS160_REG_COMMAND, ENS160_COMMAND_SETSEQ); - } else { - result = 1; + result = I2C_write8_reg(i2cAddress, ENS160_REG_COMMAND, ENS160_COMMAND_SETSEQ); + } + else { + result = false; } - return result == 0; + return result; } /////////////////////////////////////////////////////////////////////////////////////////////////// @@ -514,11 +582,13 @@ bool P164_data_struct::addCustomStep(uint16_t time, bool measureHP0, bool measur uint16_t tempHP1, uint16_t tempHP2, uint16_t tempHP3) { uint8_t seq_ack; uint8_t temp; + bool is_ok; - #ifdef P164_ENS160_DEBUG - Serial.print("setCustomMode() write step "); - Serial.println(this->_stepCount); - #endif // ifdef P164_ENS160_DEBUG + #ifdef P164_ENS160_DEBUG + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + addLogMove(LOG_LEVEL_DEBUG, concat(F("setCustomMode() write step "), this->_stepCount)); + } + #endif // ifdef P164_ENS160_DEBUG // TODO check if delay is needed // delay(ENS160_BOOTING); // Wait to boot after reset @@ -529,28 +599,28 @@ bool P164_data_struct::addCustomStep(uint16_t time, bool measureHP0, bool measur if (measureHP1) { temp = temp | 0x10; } if (measureHP2) { temp = temp | 0x8; } if (measureHP3) { temp = temp | 0x4; } - P164_data_struct::write8(i2cAddress, ENS160_REG_GPR_WRITE_0, temp); + I2C_write8_reg(i2cAddress, ENS160_REG_GPR_WRITE_0, temp); temp = (uint8_t)(((time / 24) - 1) >> 2); - P164_data_struct::write8(i2cAddress, ENS160_REG_GPR_WRITE_1, temp); + I2C_write8_reg(i2cAddress, ENS160_REG_GPR_WRITE_1, temp); - P164_data_struct::write8(i2cAddress, ENS160_REG_GPR_WRITE_2, (uint8_t)(tempHP0 / 2)); - P164_data_struct::write8(i2cAddress, ENS160_REG_GPR_WRITE_3, (uint8_t)(tempHP1 / 2)); - P164_data_struct::write8(i2cAddress, ENS160_REG_GPR_WRITE_4, (uint8_t)(tempHP2 / 2)); - P164_data_struct::write8(i2cAddress, ENS160_REG_GPR_WRITE_5, (uint8_t)(tempHP3 / 2)); + I2C_write8_reg(i2cAddress, ENS160_REG_GPR_WRITE_2, (uint8_t)(tempHP0 / 2)); + I2C_write8_reg(i2cAddress, ENS160_REG_GPR_WRITE_3, (uint8_t)(tempHP1 / 2)); + I2C_write8_reg(i2cAddress, ENS160_REG_GPR_WRITE_4, (uint8_t)(tempHP2 / 2)); + I2C_write8_reg(i2cAddress, ENS160_REG_GPR_WRITE_5, (uint8_t)(tempHP3 / 2)); - P164_data_struct::write8(i2cAddress, ENS160_REG_GPR_WRITE_6, (uint8_t)(this->_stepCount - 1)); + I2C_write8_reg(i2cAddress, ENS160_REG_GPR_WRITE_6, (uint8_t)(this->_stepCount - 1)); if (this->_stepCount == 1) { - P164_data_struct::write8(i2cAddress, ENS160_REG_GPR_WRITE_7, 128); + I2C_write8_reg(i2cAddress, ENS160_REG_GPR_WRITE_7, 128); } else { - P164_data_struct::write8(i2cAddress, ENS160_REG_GPR_WRITE_7, 0); + I2C_write8_reg(i2cAddress, ENS160_REG_GPR_WRITE_7, 0); } // TODO check if delay is needed // delay(ENS160_BOOTING); + seq_ack = I2C_read8_reg(i2cAddress, ENS160_REG_GPR_READ_7, &is_ok); - seq_ack = P164_data_struct::read8(i2cAddress, ENS160_REG_GPR_READ_7); // TODO check if delay is needed // delay(ENS160_BOOTING); // Wait to boot after reset @@ -568,37 +638,27 @@ bool P164_data_struct::addCustomStep(uint16_t time, bool measureHP0, bool measur /////////////////////////////////////////////////////////////////////////////////////////////////// bool P164_data_struct::measureRaw() { uint8_t i2cbuf[8]; - uint8_t status; bool newData = false; - - // Set default status for early bail out - #ifdef P164_ENS160_DEBUG - Serial.println("ENS16x: Start measurement"); - #endif // ifdef P164_ENS160_DEBUG + bool is_ok = true; if (this->_state == P164_STATE_OPERATIONAL) { this->getStatus(); - status = this->_statusReg; - - if (IS_NEWGPR(status)) { + if (IS_NEWGPR(this->_statusReg)) { newData = true; // Read raw resistance values - P164_data_struct::read(i2cAddress, ENS160_REG_GPR_READ_0, i2cbuf, 8); - _hp0_rs = CONVERT_RS_RAW2OHMS_F((uint32_t)(i2cbuf[0] | ((uint16_t)i2cbuf[1] << 8))); - _hp1_rs = CONVERT_RS_RAW2OHMS_F((uint32_t)(i2cbuf[2] | ((uint16_t)i2cbuf[3] << 8))); - _hp2_rs = CONVERT_RS_RAW2OHMS_F((uint32_t)(i2cbuf[4] | ((uint16_t)i2cbuf[5] << 8))); - _hp3_rs = CONVERT_RS_RAW2OHMS_F((uint32_t)(i2cbuf[6] | ((uint16_t)i2cbuf[7] << 8))); + _hp0_rs = CONVERT_RS_RAW2OHMS_F(I2C_read16_LE_reg(i2cAddress, ENS160_REG_GPR_READ_0, &is_ok)); + _hp1_rs = CONVERT_RS_RAW2OHMS_F(I2C_read16_LE_reg(i2cAddress, ENS160_REG_GPR_READ_2, &is_ok)); + _hp2_rs = CONVERT_RS_RAW2OHMS_F(I2C_read16_LE_reg(i2cAddress, ENS160_REG_GPR_READ_4, &is_ok)); + _hp3_rs = CONVERT_RS_RAW2OHMS_F(I2C_read16_LE_reg(i2cAddress, ENS160_REG_GPR_READ_6, &is_ok)); // Read baselines - P164_data_struct::read(i2cAddress, ENS160_REG_DATA_BL, i2cbuf, 8); - _hp0_bl = CONVERT_RS_RAW2OHMS_F((uint32_t)(i2cbuf[0] | ((uint16_t)i2cbuf[1] << 8))); - _hp1_bl = CONVERT_RS_RAW2OHMS_F((uint32_t)(i2cbuf[2] | ((uint16_t)i2cbuf[3] << 8))); - _hp2_bl = CONVERT_RS_RAW2OHMS_F((uint32_t)(i2cbuf[4] | ((uint16_t)i2cbuf[5] << 8))); - _hp3_bl = CONVERT_RS_RAW2OHMS_F((uint32_t)(i2cbuf[6] | ((uint16_t)i2cbuf[7] << 8))); - - P164_data_struct::read(i2cAddress, ENS160_REG_DATA_MISR, i2cbuf, 1); - _misr = i2cbuf[0]; + _hp0_bl = CONVERT_RS_RAW2OHMS_F(I2C_read16_LE_reg(i2cAddress, ENS160_REG_DATA_BL+0, &is_ok)); + _hp1_bl = CONVERT_RS_RAW2OHMS_F(I2C_read16_LE_reg(i2cAddress, ENS160_REG_DATA_BL+2, &is_ok)); + _hp2_bl = CONVERT_RS_RAW2OHMS_F(I2C_read16_LE_reg(i2cAddress, ENS160_REG_DATA_BL+4, &is_ok)); + _hp3_bl = CONVERT_RS_RAW2OHMS_F(I2C_read16_LE_reg(i2cAddress, ENS160_REG_DATA_BL+6, &is_ok)); + + _misr = I2C_read8_reg(i2cAddress, ENS160_REG_DATA_MISR, &is_ok); } } @@ -630,7 +690,7 @@ bool P164_data_struct::set_envdata210(uint16_t t, uint16_t h) { trh_in[2] = h & 0xff; // RH_IN LSB trh_in[3] = (h >> 8) & 0xff; // RH_IN MSB - uint8_t result = P164_data_struct::write(i2cAddress, ENS160_REG_TEMP_IN, trh_in, 4); + uint8_t result = I2C_writeBytes_reg(i2cAddress, ENS160_REG_TEMP_IN, trh_in, 4); return result; } @@ -638,21 +698,33 @@ bool P164_data_struct::set_envdata210(uint16_t t, uint16_t h) { /////////////////////////////////////////////////////////////////////////////////////////////////// // Read firmware revision // // Precondition: Device MODE is IDLE // +// Note: This is according to original Sciosense software and matches ENS161 datasheet // +// The ENS160 datasheet is unclear, suggesting GRP_READ0 and GRP_READ1 registers // /////////////////////////////////////////////////////////////////////////////////////////////////// bool P164_data_struct::getFirmware() { - uint8_t i2cbuf[3]; - uint8_t result; - - result = P164_data_struct::write8(i2cAddress, ENS160_REG_COMMAND, ENS160_COMMAND_GET_APPVER); - - if (result == 0) { - result = P164_data_struct::read(i2cAddress, ENS160_REG_GPR_READ_4, i2cbuf, 3); + bool result = false; // Build return value for function + bool is_ok; // Temporary flag to track status + unsigned long ts; // Timestamp to limit polling time + + result = I2C_write8_reg(i2cAddress, ENS160_REG_COMMAND, ENS160_COMMAND_GET_APPVER); + + // Wait till GRP_READ registers are available. + is_ok = result; + ts = millis(); + while (is_ok) { // Abuse is_ok to keep polling until: + is_ok &= this->getStatus(); // - Device is inaccessible + is_ok &= (! IS_NEWGPR(this->_statusReg)); // - Firmware data is available + is_ok &= (timePassedSince(ts) < ENS160_BOOTING); // - Timeout occured + } + if (! IS_NEWGPR(this->_statusReg)) { // Check if firmware could be read + result = false; + addLogMove(LOG_LEVEL_ERROR, F("P164: Could not read firmware version")); } - if (result == 0) { - this->_fw_ver_major = i2cbuf[0]; - this->_fw_ver_minor = i2cbuf[1]; - this->_fw_ver_build = i2cbuf[2]; + if (result) { + this->_fw_ver_major = I2C_read8_reg(i2cAddress, ENS160_REG_GPR_READ_4, &is_ok); + this->_fw_ver_minor = I2C_read8_reg(i2cAddress, ENS160_REG_GPR_READ_5, &is_ok); + this->_fw_ver_build = I2C_read8_reg(i2cAddress, ENS160_REG_GPR_READ_6, &is_ok); } else { this->_fw_ver_major = 0; @@ -661,55 +733,50 @@ bool P164_data_struct::getFirmware() { } #ifdef P164_ENS160_DEBUG - Serial.print(F("P164: getFirmware() result: ")); - Serial.print(result == 0 ? "ok," : "nok, major= "); - Serial.print(this->_fw_ver_major); - Serial.print(F(", minor=")); - Serial.print(this->_fw_ver_minor); - Serial.print(F(", build=")); - Serial.println(this->_fw_ver_build); - #endif // ifdef P164_ENS160_DEBUG - - return result == 0; -} - -/////////////////////////////////////////////////////////////////////////////////////////////////// -// Set operation mode of sensor // -// Note: This function is to set the operation mode to the plugin software structure only // -// The statemachine shall handle the actual programming of the OPMODE register // -/////////////////////////////////////////////////////////////////////////////////////////////////// -bool P164_data_struct::setMode(uint8_t mode) { - bool result = false; - - // LP only valid for rev>0 - if (!(mode == ENS160_OPMODE_LP) and (_revENS16x == 0)) { - this->_opmode = mode; - result = true; - } - - #ifdef P164_ENS160_DEBUG - Serial.print(F("P164: setMode(")); - Serial.print(mode); - Serial.println(F(")")); + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + String log; + log += F("P164: getFirmware() result: "); + log += result ? F("ok") : F("nok"); + log += F(", major="); + log += this->_fw_ver_major; + log += F(", minor="); + log += this->_fw_ver_minor; + log += F(", build="); + log += this->_fw_ver_build; + log += F(", registers="); + for (int i=0; i<8; i++) + { + log += formatToHex_decimal(I2C_read8_reg(i2cAddress, ENS160_REG_GPR_READ_0+i, &is_ok)); + log += ", "; + } + addLogMove(LOG_LEVEL_DEBUG, log); + } #endif // ifdef P164_ENS160_DEBUG return result; } + /////////////////////////////////////////////////////////////////////////////////////////////////// // Write to opmode register of the device // /////////////////////////////////////////////////////////////////////////////////////////////////// bool P164_data_struct::writeMode(uint8_t mode) { - uint8_t result; + bool result; - result = P164_data_struct::write8(i2cAddress, ENS160_REG_OPMODE, mode); + result = I2C_write8_reg(i2cAddress, ENS160_REG_OPMODE, mode); #ifdef P164_ENS160_DEBUG - Serial.print(F("P164: writeMode() result: ")); - Serial.println(result == 0 ? F("ok") : F("nok")); + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + String log; + log += F("P164: writeMode("); + log += mode; + log += F(") result: "); + log += result ? F("ok") : F("nok"); + addLogMove(LOG_LEVEL_DEBUG, log); + } #endif // ifdef P164_ENS160_DEBUG - return result == 0; + return result; } /////////////////////////////////////////////////////////////////////////////////////////////////// @@ -720,10 +787,13 @@ bool P164_data_struct::checkPartID(void) { uint16_t part_id; // Resulting PartID bool result = false; - P164_data_struct::read(i2cAddress, ENS160_REG_PART_ID, i2cbuf, 2); - part_id = i2cbuf[0] | ((uint16_t)i2cbuf[1] << 8); + part_id = I2C_read16_LE_reg(i2cAddress, ENS160_REG_PART_ID, &result); - if (part_id == ENS160_PARTID) { + if (!result) { + this->_revENS16x = 0xFF; + result = false; + } + else if (part_id == ENS160_PARTID) { this->_revENS16x = 0; result = true; } @@ -737,10 +807,14 @@ bool P164_data_struct::checkPartID(void) { } #ifdef P164_ENS160_DEBUG - Serial.print(F("P164: checkPartID() result: ")); - if (part_id == ENS160_PARTID) { Serial.println(F("ENS160")); } - else if (part_id == ENS161_PARTID) { Serial.println(F("ENS161")); } - else { Serial.println(F("no valid part ID read")); } + String log; + log += F("P164: checkPartID() result: "); + switch (part_id) { + case ENS160_PARTID: log += F("ENS160"); break; + case ENS161_PARTID: log += F("ENS161"); break; + default: log += F("no valid part ID read"); break; + } + addLogMove(LOG_LEVEL_DEBUG, log); #endif // ifdef P164_ENS160_DEBUG return result; @@ -752,11 +826,12 @@ bool P164_data_struct::checkPartID(void) { bool P164_data_struct::clearCommand(void) { bool result = false; - result = P164_data_struct::write8(i2cAddress, ENS160_REG_COMMAND, ENS160_COMMAND_NOP); - result = P164_data_struct::write8(i2cAddress, ENS160_REG_COMMAND, ENS160_COMMAND_CLRGPR); + result = I2C_write8_reg(i2cAddress, ENS160_REG_COMMAND, ENS160_COMMAND_NOP); + result = I2C_write8_reg(i2cAddress, ENS160_REG_COMMAND, ENS160_COMMAND_CLRGPR); #ifdef P164_ENS160_DEBUG - Serial.print(F("P164: clearCommand() result: ")); - Serial.println(result == 0 ? "ok" : "nok"); + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + addLogMove(LOG_LEVEL_DEBUG, concat(F("P164: clearCommand() result: "), result ? "ok" : "nok")); + } #endif // ifdef P164_ENS160_DEBUG return result; @@ -770,24 +845,27 @@ bool P164_data_struct::getStatus() bool ret = false; uint8_t val = 0; - //this->_statusReg = P164_data_struct::read8(i2cAddress, ENS160_REG_DATA_STATUS); - ret = P164_data_struct::read(i2cAddress, ENS160_REG_DATA_STATUS, &val, 1); + val = I2C_read8_reg(i2cAddress, ENS160_REG_DATA_STATUS, &ret); this->_statusReg = val; #ifdef P164_ENS160_DEBUG - Serial.print(F("P164: Status register: 0x")); - Serial.print(this->_statusReg, HEX); - Serial.print(F(" VALIDITY: ")); - Serial.print(GET_STATUS_VALIDITY(this->_statusReg) ); - Serial.print(F(" STATAS: ")); - Serial.print((this->_statusReg & ENS160_STATUS_STATAS) == ENS160_STATUS_STATAS); - Serial.print(F(" STATER: ")); - Serial.print((this->_statusReg & ENS160_STATUS_STATER) == ENS160_STATUS_STATER); - Serial.print(F(" NEWDAT: ")); - Serial.print((this->_statusReg & ENS160_STATUS_NEWDAT) == ENS160_STATUS_NEWDAT); - Serial.print(F(" NEWGRP: ")); - Serial.print((this->_statusReg & ENS160_STATUS_NEWGPR) == ENS160_STATUS_NEWGPR); - Serial.print(F(" return: ")); - Serial.println(ret); + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + String log; + log += F("P164: Status register: "); + log += formatToHex_decimal(this->_statusReg, HEX); + log += F(", VALIDITY: "); + log += GET_STATUS_VALIDITY(this->_statusReg); + log += F(", STATAS: "); + log += (this->_statusReg & ENS160_STATUS_STATAS) == ENS160_STATUS_STATAS; + log += F(", STATER: "); + log += (this->_statusReg & ENS160_STATUS_STATER) == ENS160_STATUS_STATER; + log += F(", NEWDAT: "); + log += (this->_statusReg & ENS160_STATUS_NEWDAT) == ENS160_STATUS_NEWDAT; + log += F(", NEWGRP: "); + log += (this->_statusReg & ENS160_STATUS_NEWGPR) == ENS160_STATUS_NEWGPR; + log += F(", return: "); + log += ret; + addLogMove(LOG_LEVEL_DEBUG, log); + } #endif // ifdef P164_ENS160_DEBUG return ret; } @@ -796,33 +874,25 @@ bool P164_data_struct::getStatus() // I2C functionality copied from Sciosense. TODO: Refactor I2C to use the ESPEasy standard // /////////////////////////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////////////////////////// -// Read one byte from a device register // -// Return: byte read // -// Note: No indication I2C operation was successful // -/////////////////////////////////////////////////////////////////////////////////////////////////// -uint8_t P164_data_struct::read8(uint8_t addr, byte reg) { - uint8_t ret; - - (void)P164_data_struct::read(addr, reg, &ret, 1); - - return ret; -} - /////////////////////////////////////////////////////////////////////////////////////////////////// // Read a consecutive range of registers from device // // Return: boolean == true when I2C transaction was succesful // +// Note: This functionality is not found in ESPEasy I2C library // /////////////////////////////////////////////////////////////////////////////////////////////////// -bool P164_data_struct::read(uint8_t addr, uint8_t reg, uint8_t *buf, uint8_t num) { +#ifdef P164_LEGACY_CODE +bool P164_data_struct::readI2C(uint8_t addr, uint8_t reg, uint8_t *buf, uint8_t num) { uint8_t pos = 0; uint8_t result = 0; #ifdef P164_ENS160_DEBUG - Serial.print(F("P164: I2C read address: 0x")); - Serial.print(addr, HEX); - Serial.print(F(", register: 0x")); - Serial.print(reg, HEX); - Serial.print(F(" data:")); + String log; // Debug String concatenated in multiple sections. Keep outside local if statement + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + log += F("P164: I2C read address: "); + log += formatToHex_decimal(addr); + log += F(", register: "); + log += formatToHex_decimal(reg); + log += F(" data:"); + } #endif // ifdef P164_ENS160_DEBUG // on arduino we need to read in 32 byte chunks @@ -836,54 +906,24 @@ bool P164_data_struct::read(uint8_t addr, uint8_t reg, uint8_t *buf, uint8_t num for (int i = 0; i < read_now; i++) { buf[pos] = Wire.read(); - #ifdef P164_ENS160_DEBUG - Serial.print(" 0x"); - Serial.print(buf[pos], HEX); - #endif // ifdef P164_ENS160_DEBUG + #ifdef P164_ENS160_DEBUG + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + log += F(" "); + log += formatToHex_decimal(buf[pos]); + } + #endif // ifdef P164_ENS160_DEBUG pos++; } } - #ifdef P164_ENS160_DEBUG - Serial.println("."); - #endif // ifdef P164_ENS160_DEBUG - - return result == 0; -} - -/////////////////////////////////////////////////////////////////////////////////////////////////// -// Write one byte to a device register // -// Return: boolean == true when I2C transaction was succesful // -/////////////////////////////////////////////////////////////////////////////////////////////////// -bool P164_data_struct::write8(uint8_t addr, byte reg, byte value) { - return P164_data_struct::write(addr, reg, &value, 1); -} - -/////////////////////////////////////////////////////////////////////////////////////////////////// -// Write a consecutive range of registers to device // -// Return: boolean == true when I2C transaction was succesful // -/////////////////////////////////////////////////////////////////////////////////////////////////// -bool P164_data_struct::write(uint8_t addr, uint8_t reg, uint8_t *buf, uint8_t num) { - uint8_t result; - #ifdef P164_ENS160_DEBUG - Serial.print(F("P164: I2C write address: 0x")); - Serial.print(addr, HEX); - Serial.print(F(", register: 0x")); - Serial.print(reg, HEX); - Serial.print(F(", value:")); - - for (int i = 0; i < num; i++) { - Serial.print(F(" 0x")); - Serial.print(buf[i], HEX); + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + log += F("."); + addLogMove(LOG_LEVEL_DEBUG, log); } - Serial.println(); #endif // ifdef P164_ENS160_DEBUG - Wire.beginTransmission((uint8_t)addr); - Wire.write((uint8_t)reg); - Wire.write((uint8_t *)buf, num); - result = Wire.endTransmission(); - return result; + return result == 0; } +#endif // P164_LEGACY_CODE #endif // ifdef USES_P164 diff --git a/src/src/PluginStructs/P164_data_struct.h b/src/src/PluginStructs/P164_data_struct.h index 188cf7f24c..2203d23b3f 100644 --- a/src/src/PluginStructs/P164_data_struct.h +++ b/src/src/PluginStructs/P164_data_struct.h @@ -15,10 +15,12 @@ #ifdef USES_P164 //#define P164_USE_CUSTOMMODE // Enable usage of ENS160 custom modes -#define P164_ENS160_DEBUG // Enable debugging using the serial port +#ifndef BUILD_NO_DEBUG +//#define P164_ENS160_DEBUG // Enable debugging using the serial port +#endif // ESPEasy plugin parameter storage -#define P164_I2C_ADDR PCONFIG(0) +#define P164_PCONFIG_I2C_ADDR PCONFIG(0) #define P164_PCONFIG_TEMP_TASK PCONFIG(1) #define P164_PCONFIG_TEMP_VAL PCONFIG(2) #define P164_PCONFIG_HUM_TASK PCONFIG(3) @@ -35,6 +37,8 @@ enum P164_state P164_STATE_INITIAL, // Device is in an unknown state, typically after reset P164_STATE_ERROR, // Device is in an error state P164_STATE_RESETTING, // Waiting for response after reset + P164_STATE_STARTING1, // Warming up after reset, wait to become idle + P164_STATE_STARTING2, // Startup state waiting for previous command to be finished P164_STATE_IDLE, // Device is brought into IDLE mode P164_STATE_DEEPSLEEP, // Device is brought into DEEPSLEEP mode P164_STATE_OPERATIONAL, // Device is brought into OPERATIONAL mode @@ -93,7 +97,6 @@ struct P164_data_struct : public PluginTaskData_base { void moveToState(P164_state newState); // Trigger a state change uint8_t i2cAddress = ENS160_I2CADDR_0; // The I2C address of the connected device - uint8_t _nINT = 0; // INT pin number (0: not used) P164_state _state = P164_STATE_INITIAL; // General state of the software ulong _lastChange = 0u; // Timestamp of last state transition @@ -127,11 +130,7 @@ struct P164_data_struct : public PluginTaskData_base { #endif // P164_USE_CUSTOMMODE // I2C access functions - static uint8_t read8(uint8_t addr, byte reg); - static bool read(uint8_t addr, uint8_t reg, uint8_t *buf, uint8_t num); - static bool write8(uint8_t addr, byte reg, byte value); - static bool write(uint8_t addr, uint8_t reg, uint8_t *buf, uint8_t num); - + static bool readI2C(uint8_t addr, uint8_t reg, uint8_t *buf, uint8_t num); }; #endif // ifdef USES_P164 #endif // ifndef PLUGINSTRUCTS_P164_DATA_STRUCT_H From b979ef969575b0853ab4aac7e748ed13afb8cc85 Mon Sep 17 00:00:00 2001 From: flashmark <15078748+flashmark@users.noreply.github.com> Date: Thu, 28 Dec 2023 08:00:32 +0100 Subject: [PATCH 05/10] Fixed compiler issue after rebase --- src/_P164_gases_ens160.ino | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/_P164_gases_ens160.ino b/src/_P164_gases_ens160.ino index d7ca63f143..adff704b24 100644 --- a/src/_P164_gases_ens160.ino +++ b/src/_P164_gases_ens160.ino @@ -110,13 +110,18 @@ boolean Plugin_164(uint8_t function, struct EventStruct *event, String& string) float temperature = 20.0f; // A reasonable value in case temperature source task is invalid float humidity = 50.0f; // A reasonable value in case humidity source task is invalid + float tvoc = 0.0f; + float eco2 = 0.0f; + if (validTaskIndex(P164_PCONFIG_TEMP_TASK) && validTaskIndex(P164_PCONFIG_HUM_TASK)) { // we're checking a var from another task, so calculate that basevar temperature = UserVar.getFloat(P164_PCONFIG_TEMP_TASK, P164_PCONFIG_TEMP_VAL); // in degrees C humidity = UserVar.getFloat(P164_PCONFIG_HUM_TASK, P164_PCONFIG_HUM_VAL); // in % relative } - success = P164_data->read(UserVar[event->BaseVarIndex], UserVar[event->BaseVarIndex + 1], temperature, humidity); + success = P164_data->read(tvoc, eco2, temperature, humidity); + UserVar.setFloat(event->TaskIndex, 0, tvoc); + UserVar.setFloat(event->TaskIndex, 1, eco2); break; } From 6441ed8761127f175e2313caa10a1b5620c04da9 Mon Sep 17 00:00:00 2001 From: flashmark <15078748+flashmark@users.noreply.github.com> Date: Thu, 28 Dec 2023 08:54:44 +0100 Subject: [PATCH 06/10] Minor cleanup --- src/_P164_gases_ens160.ino | 14 +++++++------- src/src/PluginStructs/P164_data_struct.cpp | 19 ++++++++++++++----- src/src/PluginStructs/P164_data_struct.h | 6 +++--- 3 files changed, 24 insertions(+), 15 deletions(-) diff --git a/src/_P164_gases_ens160.ino b/src/_P164_gases_ens160.ino index adff704b24..322b4e20d8 100644 --- a/src/_P164_gases_ens160.ino +++ b/src/_P164_gases_ens160.ino @@ -41,7 +41,7 @@ boolean Plugin_164(uint8_t function, struct EventStruct *event, String& string) Device[deviceCount].TimerOption = true; Device[deviceCount].GlobalSyncOption = true; Device[deviceCount].PluginStats = true; - Device[deviceCount].I2CNoDeviceCheck = true; //TODO + Device[deviceCount].I2CNoDeviceCheck = true; break; } @@ -61,7 +61,7 @@ boolean Plugin_164(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_I2C_HAS_ADDRESS: case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: { - const uint8_t i2cAddressValues[] = { ENS160_I2CADDR_0, ENS160_I2CADDR_1 }; + const uint8_t i2cAddressValues[] = { P164_ENS160_I2CADDR_0, P164_ENS160_I2CADDR_1 }; constexpr int nrAddressOptions = NR_ELEMENTS(i2cAddressValues); if (function == PLUGIN_WEBFORM_SHOW_I2C_PARAMS) { @@ -85,7 +85,7 @@ boolean Plugin_164(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_SET_DEFAULTS: { - P164_PCONFIG_I2C_ADDR = ENS160_I2CADDR_1; + P164_PCONFIG_I2C_ADDR = P164_ENS160_I2CADDR_1; success = true; break; } @@ -104,18 +104,18 @@ boolean Plugin_164(uint8_t function, struct EventStruct *event, String& string) P164_data_struct *P164_data = static_cast(getPluginTaskData(event->TaskIndex)); if (nullptr == P164_data) { - addLogMove(LOG_LEVEL_ERROR, "P164: plugin_read NULLPTR"); + addLogMove(LOG_LEVEL_ERROR, F("P164: plugin_read NULLPTR")); break; } float temperature = 20.0f; // A reasonable value in case temperature source task is invalid float humidity = 50.0f; // A reasonable value in case humidity source task is invalid - float tvoc = 0.0f; - float eco2 = 0.0f; + float tvoc = 0.0f; // tvoc value to be retrieved from device + float eco2 = 0.0f; // eCO2 value to be retrieved from device if (validTaskIndex(P164_PCONFIG_TEMP_TASK) && validTaskIndex(P164_PCONFIG_HUM_TASK)) { - // we're checking a var from another task, so calculate that basevar + // we're checking a value from other tasks temperature = UserVar.getFloat(P164_PCONFIG_TEMP_TASK, P164_PCONFIG_TEMP_VAL); // in degrees C humidity = UserVar.getFloat(P164_PCONFIG_HUM_TASK, P164_PCONFIG_HUM_VAL); // in % relative } diff --git a/src/src/PluginStructs/P164_data_struct.cpp b/src/src/PluginStructs/P164_data_struct.cpp index 2d9debd0ef..0f2bad00bc 100644 --- a/src/src/PluginStructs/P164_data_struct.cpp +++ b/src/src/PluginStructs/P164_data_struct.cpp @@ -25,6 +25,8 @@ // A curious delay inserted in the original code [ms] #define ENS160_BOOTING 10 +// Max time for device to react on a reset [ms] +#define ENS160_MAXBOOTING 2000 // ENS160 registers for version V0 #define ENS160_REG_PART_ID 0x00 // 2 byte register for part identification @@ -79,7 +81,7 @@ #define ENS160_OPMODE_ULP 0x04 // ULTRA LOW POWER (ENS161 only) #define ENS160_OPMODE_CUSTOM 0xC0 // Not specified in datasheet -// ENS160 undefined bitfields? +// ENS160 unspecified bitfields? #define ENS160_BL_CMD_START 0x02 #define ENS160_BL_CMD_ERASE_APP 0x04 #define ENS160_BL_CMD_ERASE_BLINE 0x06 @@ -89,7 +91,7 @@ #define ENS160_BL_CMD_GET_APPVER 0x0E #define ENS160_BL_CMD_EXITBL 0x12 -// ENS160 undefined bitfields? +// ENS160 unspecified bitfields? #define ENS160_SEQ_ACK_NOTCOMPLETE 0x80 #define ENS160_SEQ_ACK_COMPLETE 0xC0 @@ -97,9 +99,13 @@ #define IS_ENS160_SEQ_ACK_COMPLETE(x) (ENS160_SEQ_ACK_COMPLETE == (ENS160_SEQ_ACK_COMPLETE & (x))) // ENS160 STATUS bitfields -#define ENS160_STATUS_STATAS 0x80 // STATAS: Indicates that an OPMODE is rumming +#define ENS160_STATUS_STATAS 0x80 // STATAS: Indicates that an OPMODE is running #define ENS160_STATUS_STATER 0x40 // STATER: High indicated that an error is detected #define ENS160_STATUS_VALIDITY 0x0C // VALIDITY FLAG +#define ENS160_STATUS_VAL_NORM 0x00 // 0: Normal operation +#define ENS160_STATUS_VAL_WARM 0x01 // 1: Warm-Up phase +#define ENS160_STATUS_VAL_NOUSE 0x02 // 2: Not used +#define ENS160_STATUS_VAL_INVAL 0x03 // 3: Invalid output #define ENS160_STATUS_NEWDAT 0x02 // NEWDAT: 1= New data in data registers available #define ENS160_STATUS_NEWGPR 0x01 // NEWGRP: 1= New data in GRP_READ registers available @@ -350,13 +356,16 @@ bool P164_data_struct::evaluateState() { newState = P164_STATE_STARTING1; } + else if (timePassedSince(this->_lastChange) > ENS160_MAXBOOTING) { + newState = P164_STATE_ERROR; + } } break; case P164_STATE_STARTING1: // A valid device is found, check if its status is ready to continue this->_available = false; this->getStatus(); - if (GET_STATUS_VALIDITY(this->_statusReg) == 0) + if (GET_STATUS_VALIDITY(this->_statusReg) == ENS160_STATUS_VAL_NORM) { this->writeMode(ENS160_OPMODE_IDLE); this->clearCommand(); @@ -366,7 +375,7 @@ bool P164_data_struct::evaluateState() case P164_STATE_STARTING2: this->_available = false; this->getStatus(); - if (GET_STATUS_VALIDITY(this->_statusReg) == 0) { + if (GET_STATUS_VALIDITY(this->_statusReg) == ENS160_STATUS_VAL_NORM) { this->getFirmware(); newState = P164_STATE_IDLE; } diff --git a/src/src/PluginStructs/P164_data_struct.h b/src/src/PluginStructs/P164_data_struct.h index 2203d23b3f..e80d3c0eed 100644 --- a/src/src/PluginStructs/P164_data_struct.h +++ b/src/src/PluginStructs/P164_data_struct.h @@ -27,8 +27,8 @@ #define P164_PCONFIG_HUM_VAL PCONFIG(4) // 7-bit I2C slave address of the ENS160 -#define ENS160_I2CADDR_0 0x52 //ADDR low -#define ENS160_I2CADDR_1 0x53 //ADDR high +#define P164_ENS160_I2CADDR_0 0x52 //ADDR low +#define P164_ENS160_I2CADDR_1 0x53 //ADDR high // Use a state machine to avoid blocking the CPU while waiting for the response // See P164_data_struct.cpp for detailed description @@ -96,7 +96,7 @@ struct P164_data_struct : public PluginTaskData_base { bool writeMode(uint8_t mode); // Write the opmode register void moveToState(P164_state newState); // Trigger a state change - uint8_t i2cAddress = ENS160_I2CADDR_0; // The I2C address of the connected device + uint8_t i2cAddress = P164_ENS160_I2CADDR_0; // The I2C address of the connected device P164_state _state = P164_STATE_INITIAL; // General state of the software ulong _lastChange = 0u; // Timestamp of last state transition From 4e2cab00708906601da7481e630549449debafde Mon Sep 17 00:00:00 2001 From: flashmark <15078748+flashmark@users.noreply.github.com> Date: Thu, 28 Dec 2023 14:38:58 +0100 Subject: [PATCH 07/10] Some fixes after review --- src/_P164_gases_ens160.ino | 1 - src/src/PluginStructs/P164_data_struct.cpp | 1 - 2 files changed, 2 deletions(-) diff --git a/src/_P164_gases_ens160.ino b/src/_P164_gases_ens160.ino index 322b4e20d8..9537971a0a 100644 --- a/src/_P164_gases_ens160.ino +++ b/src/_P164_gases_ens160.ino @@ -41,7 +41,6 @@ boolean Plugin_164(uint8_t function, struct EventStruct *event, String& string) Device[deviceCount].TimerOption = true; Device[deviceCount].GlobalSyncOption = true; Device[deviceCount].PluginStats = true; - Device[deviceCount].I2CNoDeviceCheck = true; break; } diff --git a/src/src/PluginStructs/P164_data_struct.cpp b/src/src/PluginStructs/P164_data_struct.cpp index 0f2bad00bc..34c2641724 100644 --- a/src/src/PluginStructs/P164_data_struct.cpp +++ b/src/src/PluginStructs/P164_data_struct.cpp @@ -646,7 +646,6 @@ bool P164_data_struct::addCustomStep(uint16_t time, bool measureHP0, bool measur // Perfrom raw measurement and store result in internal variables // /////////////////////////////////////////////////////////////////////////////////////////////////// bool P164_data_struct::measureRaw() { - uint8_t i2cbuf[8]; bool newData = false; bool is_ok = true; From 886b25db79c6063c15fb27f9db26a78b04765ac7 Mon Sep 17 00:00:00 2001 From: flashmark <15078748+flashmark@users.noreply.github.com> Date: Thu, 28 Dec 2023 17:02:16 +0100 Subject: [PATCH 08/10] Added to CLimate and Collection G plugin sets. Removed some leftovers. --- src/Custom-sample.h | 1 + src/src/CustomBuild/define_plugin_sets.h | 6 ++++++ src/src/PluginStructs/P164_data_struct.cpp | 14 +++++--------- src/src/PluginStructs/P164_data_struct.h | 2 +- 4 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/Custom-sample.h b/src/Custom-sample.h index f0528361c3..c15ba95100 100644 --- a/src/Custom-sample.h +++ b/src/Custom-sample.h @@ -529,6 +529,7 @@ static const char DATA_ESPEASY_DEFAULT_MIN_CSS[] PROGMEM = { // #define USES_P159 // Presence - LD2410 Radar detection +// #define USES_P164 // Gases - ENS16x TVOC/eCO2 /* ####################################################################################################### ########### Controllers diff --git a/src/src/CustomBuild/define_plugin_sets.h b/src/src/CustomBuild/define_plugin_sets.h index 5f3d0ee94d..7cf4aeb26e 100644 --- a/src/src/CustomBuild/define_plugin_sets.h +++ b/src/src/CustomBuild/define_plugin_sets.h @@ -1618,6 +1618,9 @@ To create/register a plugin, you have to : #ifndef USES_P159 #define USES_P159 // Presence - LD2410 Radar detection #endif + #ifndef USES_P164 + #define USES_P164 // Gases - ENS16x TVOC\eCO2 + #endif #endif @@ -1900,6 +1903,9 @@ To create/register a plugin, you have to : #ifndef USES_P154 #define USES_P154 // Environment - BMP3xx #endif + #ifndef USES_P164 + #define USES_P164 // Gases - ENS16x TVOC/eCO2 + #endif diff --git a/src/src/PluginStructs/P164_data_struct.cpp b/src/src/PluginStructs/P164_data_struct.cpp index 34c2641724..0818e1f6a5 100644 --- a/src/src/PluginStructs/P164_data_struct.cpp +++ b/src/src/PluginStructs/P164_data_struct.cpp @@ -508,9 +508,8 @@ bool P164_data_struct::start(uint8_t slaveaddr) // Return: true if data is fresh (first reading of new data) // /////////////////////////////////////////////////////////////////////////////////////////////////// bool P164_data_struct::measure() { - uint8_t i2cbuf[8]; - bool newData = false; - bool is_ok; + bool newData = false; // True when new data is availabl at device + bool is_ok; // Dump I2C transaction status if (this->_state == P164_STATE_OPERATIONAL) { if (this->getStatus()) { // Check status register if new data is aquired @@ -791,7 +790,6 @@ bool P164_data_struct::writeMode(uint8_t mode) { // Read the part ID from ENS160 device and check for validity // /////////////////////////////////////////////////////////////////////////////////////////////////// bool P164_data_struct::checkPartID(void) { - uint8_t i2cbuf[2]; // Buffer for returned I2C data uint16_t part_id; // Resulting PartID bool result = false; @@ -851,10 +849,8 @@ bool P164_data_struct::clearCommand(void) { bool P164_data_struct::getStatus() { bool ret = false; - uint8_t val = 0; - val = I2C_read8_reg(i2cAddress, ENS160_REG_DATA_STATUS, &ret); - this->_statusReg = val; + this->_statusReg = I2C_read8_reg(i2cAddress, ENS160_REG_DATA_STATUS, &ret); #ifdef P164_ENS160_DEBUG if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { String log; @@ -878,8 +874,9 @@ bool P164_data_struct::getStatus() return ret; } +#ifdef P164_LEGACY_CODE /////////////////////////////////////////////////////////////////////////////////////////////////// -// I2C functionality copied from Sciosense. TODO: Refactor I2C to use the ESPEasy standard // +// I2C functionality copied from Sciosense. // /////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////// @@ -887,7 +884,6 @@ bool P164_data_struct::getStatus() // Return: boolean == true when I2C transaction was succesful // // Note: This functionality is not found in ESPEasy I2C library // /////////////////////////////////////////////////////////////////////////////////////////////////// -#ifdef P164_LEGACY_CODE bool P164_data_struct::readI2C(uint8_t addr, uint8_t reg, uint8_t *buf, uint8_t num) { uint8_t pos = 0; uint8_t result = 0; diff --git a/src/src/PluginStructs/P164_data_struct.h b/src/src/PluginStructs/P164_data_struct.h index e80d3c0eed..11e4768287 100644 --- a/src/src/PluginStructs/P164_data_struct.h +++ b/src/src/PluginStructs/P164_data_struct.h @@ -16,7 +16,7 @@ //#define P164_USE_CUSTOMMODE // Enable usage of ENS160 custom modes #ifndef BUILD_NO_DEBUG -//#define P164_ENS160_DEBUG // Enable debugging using the serial port + #define P164_ENS160_DEBUG // Enable debugging using the serial port #endif // ESPEasy plugin parameter storage From 91958d51b7e745e59e0a8e06fdb37bd470e545ce Mon Sep 17 00:00:00 2001 From: flashmark <15078748+flashmark@users.noreply.github.com> Date: Sat, 3 Feb 2024 13:59:15 +0100 Subject: [PATCH 09/10] Merge --- docs/source/Plugin/_Plugin.rst | 3 ++- docs/source/Plugin/_plugin_substitutions_p16x.repl | 12 ++++++++++++ src/src/CustomBuild/define_plugin_sets.h | 4 ++-- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/docs/source/Plugin/_Plugin.rst b/docs/source/Plugin/_Plugin.rst index 0e79c91ed5..2b0e0f95d3 100644 --- a/docs/source/Plugin/_Plugin.rst +++ b/docs/source/Plugin/_Plugin.rst @@ -387,7 +387,8 @@ There are different released versions of ESP Easy: ":ref:`P154_page`","|P154_status|","P154" ":ref:`P159_page`","|P159_status|","P159" ":ref:`P164_page`","|P164_status|","P164" - + ":ref:`P166_page`","|P166_status|","P166" + Internal GPIO handling ---------------------- diff --git a/docs/source/Plugin/_plugin_substitutions_p16x.repl b/docs/source/Plugin/_plugin_substitutions_p16x.repl index b6ef18ce4e..e7a3d6da5c 100644 --- a/docs/source/Plugin/_plugin_substitutions_p16x.repl +++ b/docs/source/Plugin/_plugin_substitutions_p16x.repl @@ -11,3 +11,15 @@ .. |P164_compileinfo| replace:: `.` .. |P164_usedlibraries| replace:: `I2C` +.. |P166_name| replace:: :cyan:`GP8403 Dual channel DAC 0-10V` +.. |P166_type| replace:: :cyan:`Output` +.. |P166_typename| replace:: :cyan:`Output - GP8403 Dual channel DAC 0-10V` +.. |P166_porttype| replace:: `.` +.. |P166_status| replace:: :yellow:`COLLECTION G` +.. |P166_github| replace:: P166_GP8403.ino +.. _P166_github: https://github.com/letscontrolit/ESPEasy/blob/mega/src/_P166_GP8403.ino +.. |P166_usedby| replace:: `.` +.. |P166_shortinfo| replace:: `GP8403 Dual channel DAC 0-10V` +.. |P166_maintainer| replace:: `tonhuisman` +.. |P166_compileinfo| replace:: `.` +.. |P166_usedlibraries| replace:: `modified version of DFRobot_GP8403` \ No newline at end of file diff --git a/src/src/CustomBuild/define_plugin_sets.h b/src/src/CustomBuild/define_plugin_sets.h index 1bfae0e515..f60c4b6601 100644 --- a/src/src/CustomBuild/define_plugin_sets.h +++ b/src/src/CustomBuild/define_plugin_sets.h @@ -1910,8 +1910,8 @@ To create/register a plugin, you have to : #ifndef USES_P164 #define USES_P164 // Gases - ENS16x TVOC/eCO2 #endif - - + #ifndef USES_P166 + #define USES_P166 // Output - GP8403 DAC 0-10V // Controllers #ifndef USES_C011 From 7c3b5822558a61df64c340a493135fa6b562b467 Mon Sep 17 00:00:00 2001 From: TD-er Date: Sat, 3 Feb 2024 15:08:35 +0100 Subject: [PATCH 10/10] [Build] Fix missing #endif --- src/src/CustomBuild/define_plugin_sets.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/src/CustomBuild/define_plugin_sets.h b/src/src/CustomBuild/define_plugin_sets.h index 9a8a1faccd..e257be96df 100644 --- a/src/src/CustomBuild/define_plugin_sets.h +++ b/src/src/CustomBuild/define_plugin_sets.h @@ -1915,6 +1915,7 @@ To create/register a plugin, you have to : #endif #ifndef USES_P166 #define USES_P166 // Output - GP8403 DAC 0-10V + #endif // Controllers #ifndef USES_C011