diff --git a/src/_P167_Vindstyrka.ino b/src/_P167_Vindstyrka.ino
new file mode 100644
index 0000000000..b97fd6a47f
--- /dev/null
+++ b/src/_P167_Vindstyrka.ino
@@ -0,0 +1,561 @@
+#include "_Plugin_Helper.h"
+#ifdef USES_P167
+
+// #######################################################################################################
+// ######################## Plugin 167 IKEA Vindstyrka I2C Sensor (SEN5x) ############################
+// #######################################################################################################
+// 19-06-2023 AndiBaciu creation based upon https://github.com/RobTillaart/SHT2x
+
+# include "./src/PluginStructs/P167_data_struct.h"
+
+#define PLUGIN_167
+#define PLUGIN_ID_167 167 // plugin id
+#define PLUGIN_NAME_167 "Environment - Sensirion SEN5x (IKEA Vindstyrka)" // What will be dislpayed in the selection list
+#define PLUGIN_VALUENAME1_167 "Temperature" // variable output of the plugin. The label is in quotation marks
+#define PLUGIN_VALUENAME2_167 "Humidity" // multiple outputs are supporte
+#define PLUGIN_VALUENAME3_167 "tVOC" // multiple outputs are supported
+#define PLUGIN_VALUENAME4_167 "NOx" // multiple outputs are supported
+#define PLUGIN_VALUENAME5_167 "PM 1.0" // multiple outputs are supported
+#define PLUGIN_VALUENAME6_167 "PM 2.5" // multiple outputs are supported
+#define PLUGIN_VALUENAME7_167 "PM 4.0" // multiple outputs are supported
+#define PLUGIN_VALUENAME8_167 "PM 10.0" // multiple outputs are supported
+#define PLUGIN_VALUENAME9_167 "DewPoint" // multiple outputs are supported
+#define PLUGIN_DEFAULT_NAME_1 "IKEA_Vindstyrka"
+#define PLUGIN_DEFAULT_NAME_2 "Sensirion_SEN5x"
+
+// PIN/port configuration is stored in the following:
+// CONFIG_PIN1 - The first GPIO pin selected within the task
+// CONFIG_PIN2 - The second GPIO pin selected within the task
+// CONFIG_PIN3 - The third GPIO pin selected within the task
+// CONFIG_PORT - The port in case the device has multiple in/out pins
+//
+// Custom configuration is stored in the following:
+// PCONFIG(x)
+// x can be between 1 - 8 and can store values between -32767 - 32768 (16 bit)
+//
+// N.B. these are aliases for a longer less readable amount of code. See _Plugin_Helper.h
+//
+// PCONFIG_LABEL(x) is a function to generate a unique label used as HTML id to be able to match
+// returned values when saving a configuration.
+
+// Make accessing specific parameters more readable in the code
+// #define Pxxx_OUTPUT_TYPE_INDEX 2
+# define P167_I2C_ADDRESS PCONFIG(0)
+# define P167_I2C_ADDRESS_LABEL PCONFIG_LABEL(0)
+# define P167_MODEL PCONFIG(1)
+# define P167_MODEL_LABEL PCONFIG_LABEL(1)
+# define P167_MON_SCL_PIN PCONFIG(2)
+# define P167_MON_SCL_PIN_LABEL PCONFIG_LABEL(2)
+# define P167_QUERY1 PCONFIG(3)
+# define P167_QUERY2 PCONFIG(4)
+# define P167_QUERY3 PCONFIG(5)
+# define P167_QUERY4 PCONFIG(6)
+# define P167_SEN_FIRST PCONFIG(7)
+# define P167_SEN_ATTEMPT PCONFIG_LONG(1)
+
+
+# define P167_I2C_ADDRESS_DFLT 0x69
+# define P167_MON_SCL_PIN_DFLT 13
+# define P167_MODEL_DFLT 0 // Vindstyrka or SEN54
+# define P167_QUERY1_DFLT 0 // Temperature (C)
+# define P167_QUERY2_DFLT 1 // Humidity (%)
+# define P167_QUERY3_DFLT 5 // PM2.5 (ug/m3)
+# define P167_QUERY4_DFLT 2 // tVOC (index)
+
+
+# define P167_NR_OUTPUT_VALUES 4
+# define P167_NR_OUTPUT_OPTIONS 10
+# define P167_QUERY1_CONFIG_POS 3
+# define P167_MAX_ATTEMPT 3 // Number of tentative before declaring NAN value
+
+//# define LIMIT_BUILD_SIZE 1
+
+// These pointers may be used among multiple instances of the same plugin,
+// as long as the same settings are used.
+P167_data_struct *Plugin_167_SEN = nullptr;
+boolean Plugin_167_init = false;
+
+void IRAM_ATTR Plugin_167_interrupt();
+
+// Forward declaration helper functions
+const __FlashStringHelper* p167_getQueryString(uint8_t query);
+const __FlashStringHelper* p167_getQueryValueString(uint8_t query);
+unsigned int p167_getRegister(uint8_t query, uint8_t model);
+float p167_readVal(uint8_t query, uint8_t node, unsigned int model);
+
+
+// A plugin has to implement the following function
+
+boolean Plugin_167(uint8_t function, struct EventStruct *event, String& string)
+{
+ // function: reason the plugin was called
+ // event: ??add description here??
+ // string: ??add description here??
+
+ boolean success = false;
+
+ switch (function)
+ {
+
+
+ case PLUGIN_DEVICE_ADD:
+ {
+ // This case defines the device characteristics
+ Device[++deviceCount].Number = PLUGIN_ID_167;
+ Device[deviceCount].Type = DEVICE_TYPE_I2C;
+ Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_QUAD;
+ Device[deviceCount].Ports = 0;
+ Device[deviceCount].PullUpOption = true;
+ Device[deviceCount].InverseLogicOption = false;
+ Device[deviceCount].FormulaOption = true;
+ Device[deviceCount].ValueCount = P167_NR_OUTPUT_VALUES;
+ Device[deviceCount].SendDataOption = true;
+ Device[deviceCount].TimerOption = true;
+ Device[deviceCount].I2CNoDeviceCheck = true;
+ Device[deviceCount].GlobalSyncOption = true;
+ Device[deviceCount].PluginStats = true;
+ Device[deviceCount].OutputDataType = Output_Data_type_t::Simple;
+ break;
+ }
+
+
+ case PLUGIN_GET_DEVICENAME:
+ {
+ // return the device name
+ string = F(PLUGIN_NAME_167);
+ break;
+ }
+
+
+ case PLUGIN_GET_DEVICEVALUENAMES:
+ {
+ // called when the user opens the module configuration page
+ // it allows to add a new row for each output variable of the plugin
+ // For plugins able to choose output types, see P026_Sysinfo.ino.
+ for (uint8_t i = 0; i < VARS_PER_TASK; ++i)
+ {
+ if ( i < P167_NR_OUTPUT_VALUES)
+ {
+ uint8_t choice = PCONFIG(i + P167_QUERY1_CONFIG_POS);
+ safe_strncpy(ExtraTaskSettings.TaskDeviceValueNames[i], p167_getQueryValueString(choice), sizeof(ExtraTaskSettings.TaskDeviceValueNames[i]));
+ }
+ else
+ {
+ ZERO_FILL(ExtraTaskSettings.TaskDeviceValueNames[i]);
+ }
+ }
+ break;
+ }
+
+
+ case PLUGIN_SET_DEFAULTS:
+ {
+ // Set a default config here, which will be called when a plugin is assigned to a task.
+ P167_I2C_ADDRESS = P167_I2C_ADDRESS_DFLT;
+ P167_MODEL = P167_MODEL_DFLT;
+ P167_QUERY1 = P167_QUERY1_DFLT;
+ P167_QUERY2 = P167_QUERY2_DFLT;
+ P167_QUERY3 = P167_QUERY3_DFLT;
+ P167_QUERY4 = P167_QUERY4_DFLT;
+ P167_MON_SCL_PIN = P167_MON_SCL_PIN_DFLT;
+ P167_SEN_FIRST = 99;
+ success = true;
+ break;
+ }
+
+
+ # if FEATURE_I2C_GET_ADDRESS
+ case PLUGIN_I2C_GET_ADDRESS:
+ {
+ event->Par1 = P167_I2C_ADDRESS_DFLT;
+ success = true;
+ break;
+ }
+ # endif // if FEATURE_I2C_GET_ADDRESS
+
+
+ case PLUGIN_I2C_HAS_ADDRESS:
+ case PLUGIN_WEBFORM_SHOW_I2C_PARAMS:
+ {
+ const uint8_t i2cAddressValues[] = { P167_I2C_ADDRESS_DFLT };
+ if (function == PLUGIN_WEBFORM_SHOW_I2C_PARAMS)
+ {
+ if (P167_SEN_FIRST == event->TaskIndex) // If first SEN, serial config available
+ {
+ //addFormSelectorI2C(P167_I2C_ADDRESS_LABEL, 3, i2cAddressValues, P167_I2C_ADDRESS);
+ addFormSelectorI2C(F("i2c_addr"), 1, i2cAddressValues, P167_I2C_ADDRESS);
+ addFormNote(F("Vindstyrka, SEN54, SEN55 default i2c address: 0x69"));
+ }
+ }
+ else
+ {
+ success = intArrayContains(1, i2cAddressValues, event->Par1);
+ }
+ break;
+ }
+
+
+ case PLUGIN_WEBFORM_SHOW_GPIO_DESCR:
+ {
+ if (P167_SEN_FIRST == event->TaskIndex) // If first SEN, serial config available
+ {
+ if(P167_MODEL==0)
+ {
+ string = F("MonPin SCL: ");
+ string += formatGpioLabel(P167_MON_SCL_PIN, false);
+ }
+ }
+ success = true;
+ break;
+ }
+
+
+ case PLUGIN_WEBFORM_LOAD_OUTPUT_SELECTOR:
+ {
+ const __FlashStringHelper *options[P167_NR_OUTPUT_OPTIONS];
+ for (int i = 0; i < P167_NR_OUTPUT_OPTIONS; ++i)
+ {
+ options[i] = p167_getQueryString(i);
+ }
+ for (uint8_t i = 0; i < P167_NR_OUTPUT_VALUES; ++i)
+ {
+ const uint8_t pconfigIndex = i + P167_QUERY1_CONFIG_POS;
+ sensorTypeHelper_loadOutputSelector(event, pconfigIndex, i, P167_NR_OUTPUT_OPTIONS, options);
+ }
+ addFormNote(F("NOx is available ONLY on Sensirion SEN55 model"));
+ break;
+ }
+
+
+ case PLUGIN_WEBFORM_LOAD:
+ {
+ // this case defines what should be displayed on the web form, when this plugin is selected
+ // The user's selection will be stored in
+ // PCONFIG(x) (custom configuration)
+
+ if (Plugin_167_SEN == nullptr)
+ {
+ P167_SEN_FIRST = event->TaskIndex; // To detect if first SEN or not
+ }
+
+ if (P167_SEN_FIRST == event->TaskIndex) // If first SEN, serial config available
+ {
+ addHtml(F("
This SEN5x is the first. Its configuration of Pins will affect next SEN5x."));
+ addHtml(F("
If several SEN5x's foreseen, don't use other pins."));
+
+ const __FlashStringHelper *options_model[3] = { F("IKEA Vindstyrka"), F("Sensirion SEN54"), F("Sensirion SEN55")};
+
+ addFormSelector(F("Model Type"), P167_MODEL_LABEL, 3, options_model, nullptr, P167_MODEL);
+
+ if(P167_MODEL==0)
+ {
+ addFormPinSelect(PinSelectPurpose::Generic_input, F("MonPin SCL"), F("taskdevicepin3"), P167_MON_SCL_PIN);
+ addFormNote(F("Pin for monitoring i2c communication between Vindstyrka controller and SEN5x. (Only when Model - IKEA Vindstyrka is selected.)"));
+ }
+
+ if (Plugin_167_SEN != nullptr)
+ {
+ addRowLabel(F("Device info"));
+ String prodname;
+ String sernum;
+ uint8_t firmware;
+ Plugin_167_SEN->getEID(prodname, sernum, firmware);
+ String txt = F("ProdName: ");
+ txt += prodname;
+ txt += F(" Serial Number: ");
+ txt += sernum;
+ txt += F(" Firmware: ");
+ txt += String (firmware);
+ addHtml(txt);
+
+ addRowLabel(F("Device status"));
+ txt = F("Speed warning: ");
+ txt += String((bool)Plugin_167_SEN->getStatusInfo(sensor_speed));
+ txt += F(" , Auto Cleaning: ");
+ txt += String((bool)Plugin_167_SEN->getStatusInfo(sensor_autoclean));
+ txt += F(" , GAS Error: ");
+ txt += String((bool)Plugin_167_SEN->getStatusInfo(sensor_gas));
+ txt += F(" , RHT Error: ");
+ txt += String((bool)Plugin_167_SEN->getStatusInfo(sensor_rht));
+ txt += F(" , LASER Error: ");
+ txt += String((bool)Plugin_167_SEN->getStatusInfo(sensor_laser));
+ txt += F(" , FAN Error: ");
+ txt += String((bool)Plugin_167_SEN->getStatusInfo(sensor_fan));
+ addHtml(txt);
+
+ addRowLabel(F("Check (pass/fail/errCode)"));
+ txt = Plugin_167_SEN->getSuccCount();
+ txt += '/';
+ txt += Plugin_167_SEN->getErrCount();
+ txt += '/';
+ txt += Plugin_167_SEN->getErrCode();
+ addHtml(txt);
+
+ }
+ }
+ else
+ {
+ addHtml(F("
This SEN5x is the NOT the first. Model and Pins config are DISABLED. Configuration is available in the first SEN5x plugin."));
+ addHtml(F("
Only output value can be configured."));
+
+ //looking for FIRST task Named "IKEA_Vindstyrka or Sensirion_SEN5x"
+ //Cache.taskIndexName
+ uint8_t allready_defined=88;
+ if(P167_MODEL==0)
+ {
+ allready_defined=findTaskIndexByName(PLUGIN_DEFAULT_NAME_1);
+ }
+ else
+ {
+ allready_defined=findTaskIndexByName(PLUGIN_DEFAULT_NAME_2);
+ }
+ P167_SEN_FIRST = allready_defined;
+ }
+
+ success = true;
+ break;
+ }
+
+
+ case PLUGIN_WEBFORM_SAVE:
+ {
+ // this case defines the code to be executed when the form is submitted
+ // the plugin settings should be saved to PCONFIG(x)
+ // ping configuration should be read from CONFIG_PIN1 and stored
+
+ // Save output selector parameters.
+ for (uint8_t i = 0; i < P167_NR_OUTPUT_VALUES; ++i)
+ {
+ const uint8_t pconfigIndex = i + P167_QUERY1_CONFIG_POS;
+ const uint8_t choice = PCONFIG(pconfigIndex);
+ sensorTypeHelper_saveOutputSelector(event, pconfigIndex, i, p167_getQueryValueString(choice));
+ }
+ P167_MODEL = getFormItemInt(P167_MODEL_LABEL);
+ P167_I2C_ADDRESS = P167_I2C_ADDRESS_DFLT;
+ if(P167_MODEL==0)
+ P167_MON_SCL_PIN = getFormItemInt(F("taskdevicepin3"));
+ P167_SEN_FIRST = P167_SEN_FIRST;
+
+ if (P167_SEN_FIRST == event->TaskIndex) // For first task set default name
+ {
+ if(P167_MODEL==0)
+ {
+ strcpy(ExtraTaskSettings.TaskDeviceName, PLUGIN_DEFAULT_NAME_1); // populate default name.
+ }
+ else
+ {
+ strcpy(ExtraTaskSettings.TaskDeviceName, PLUGIN_DEFAULT_NAME_2); // populate default name.
+ }
+ }
+
+ Plugin_167_init = false; // Force device setup next time
+ success = true;
+ break;
+ }
+
+
+ case PLUGIN_INIT:
+ {
+ // this case defines code to be executed when the plugin is initialised
+ // This will fail if the set to be first taskindex is no longer enabled
+
+ if (P167_SEN_FIRST == event->TaskIndex) // If first SEN5x, config available
+ {
+ if (Plugin_167_SEN != nullptr)
+ {
+ delete Plugin_167_SEN;
+ Plugin_167_SEN = nullptr;
+ }
+
+ Plugin_167_SEN = new (std::nothrow) P167_data_struct();
+
+ if (Plugin_167_SEN != nullptr)
+ {
+ Plugin_167_SEN->setupModel(P167_MODEL);
+ Plugin_167_SEN->setupDevice(P167_I2C_ADDRESS);
+ if(P167_MODEL==0)
+ {
+ Plugin_167_SEN->setupMonPin(P167_MON_SCL_PIN);
+ pinMode(P167_MON_SCL_PIN, INPUT_PULLUP);
+ attachInterrupt(P167_MON_SCL_PIN, Plugin_167_interrupt, RISING);
+ }
+ Plugin_167_SEN->reset();
+ }
+ }
+
+ //UserVar[event->BaseVarIndex] = NAN;
+ //UserVar[event->BaseVarIndex + 1] = NAN;
+ //UserVar[event->BaseVarIndex + 2] = NAN;
+ //UserVar[event->BaseVarIndex + 3] = NAN;
+ UserVar.setFloat(event->BaseVarIndex, 0, NAN);
+ UserVar.setFloat(event->BaseVarIndex, 1, NAN);
+ UserVar.setFloat(event->BaseVarIndex, 2, NAN);
+ UserVar.setFloat(event->BaseVarIndex, 3, NAN);
+
+ success = true;
+ Plugin_167_init = true;
+
+ break;
+ }
+
+
+ case PLUGIN_EXIT:
+ {
+ if (P167_SEN_FIRST == event->TaskIndex) // If first SEN5x, config available
+ {
+ if (Plugin_167_SEN != nullptr)
+ {
+ if(P167_MODEL==0)
+ {
+ Plugin_167_SEN->disableInterrupt_monpin();
+ }
+ delete Plugin_167_SEN;
+ Plugin_167_SEN = nullptr;
+ }
+ }
+
+ success = true;
+ break;
+ }
+
+
+ case PLUGIN_READ:
+ {
+ // code to be executed to read data
+ // It is executed according to the delay configured on the device configuration page, only once
+
+ if(event->TaskIndex!=P167_SEN_FIRST)
+ {
+ //All the DATA are in the first task of IKEA_Vindstyrka
+ //so all you have to do is to load this data in the current taskindex data
+ initPluginTaskData(event->TaskIndex, getPluginTaskData(P167_SEN_FIRST) );
+ }
+
+ if (nullptr != Plugin_167_SEN)
+ {
+ if (Plugin_167_SEN->inError())
+ {
+ //UserVar[event->BaseVarIndex] = NAN;
+ //UserVar[event->BaseVarIndex + 1] = NAN;
+ //UserVar[event->BaseVarIndex + 2] = NAN;
+ //UserVar[event->BaseVarIndex + 3] = NAN;
+ UserVar.setFloat(event->BaseVarIndex, 0, NAN);
+ UserVar.setFloat(event->BaseVarIndex, 1, NAN);
+ UserVar.setFloat(event->BaseVarIndex, 2, NAN);
+ UserVar.setFloat(event->BaseVarIndex, 3, NAN);
+ addLog(LOG_LEVEL_ERROR, F("Vindstyrka / SEN5X: in Error!"));
+ }
+ else
+ {
+ if(event->TaskIndex==P167_SEN_FIRST)
+ {
+ Plugin_167_SEN->startMeasurements(); // getting ready for another read cycle
+ }
+
+ //UserVar[event->BaseVarIndex] = Plugin_167_SEN->getRequestedValue(P167_QUERY1);
+ //UserVar[event->BaseVarIndex + 1] = Plugin_167_SEN->getRequestedValue(P167_QUERY2);
+ //UserVar[event->BaseVarIndex + 2] = Plugin_167_SEN->getRequestedValue(P167_QUERY3);
+ //UserVar[event->BaseVarIndex + 3] = Plugin_167_SEN->getRequestedValue(P167_QUERY4);
+ UserVar.setFloat(event->BaseVarIndex, 0, Plugin_167_SEN->getRequestedValue(P167_QUERY1));
+ UserVar.setFloat(event->BaseVarIndex, 1, Plugin_167_SEN->getRequestedValue(P167_QUERY2));
+ UserVar.setFloat(event->BaseVarIndex, 2, Plugin_167_SEN->getRequestedValue(P167_QUERY3));
+ UserVar.setFloat(event->BaseVarIndex, 3, Plugin_167_SEN->getRequestedValue(P167_QUERY4));
+ }
+ }
+
+ success = true;
+ break;
+ }
+
+
+ case PLUGIN_ONCE_A_SECOND:
+ {
+ // code to be executed once a second. Tasks which do not require fast response can be added here
+ success = true;
+ }
+
+
+ case PLUGIN_TEN_PER_SECOND:
+ {
+ // code to be executed 10 times per second. Tasks which require fast response can be added here
+ // be careful on what is added here. Heavy processing will result in slowing the module down!
+ success = true;
+ }
+
+
+ case PLUGIN_FIFTY_PER_SECOND:
+ {
+ // code to be executed 10 times per second. Tasks which require fast response can be added here
+ // be careful on what is added here. Heavy processing will result in slowing the module down!
+ if(event->TaskIndex==P167_SEN_FIRST)
+ {
+ if (nullptr != Plugin_167_SEN)
+ {
+ Plugin_167_SEN->monitorSCL(); // Vind / SEN5X FSM evaluation
+ Plugin_167_SEN->update();
+ }
+ }
+ success = true;
+ }
+ } // switch
+
+ return success;
+} // function
+
+
+
+/// @brief
+/// @param query
+/// @return
+const __FlashStringHelper* p167_getQueryString(uint8_t query)
+{
+ switch(query)
+ {
+ case 0: return F("Temperature (C)");
+ case 1: return F("Humidity (% RH)");
+ case 2: return F("tVOC (VOC index)");
+ case 3: return F("NOx (NOx index)");
+ case 4: return F("PM 1.0 (ug/m3)");
+ case 5: return F("PM 2.5 (ug/m3)");
+ case 6: return F("PM 4.0 (ug/m3)");
+ case 7: return F("PM 10.0 (ug/m3)");
+ case 8: return F("DewPoint (C)");
+ }
+ return F("");
+}
+
+/// @brief
+/// @param query
+/// @return
+const __FlashStringHelper* p167_getQueryValueString(uint8_t query)
+{
+ switch(query)
+ {
+ case 0: return F("Temperature");
+ case 1: return F("Humidity");
+ case 2: return F("tVOC");
+ case 3: return F("NOx");
+ case 4: return F("PM1p0");
+ case 5: return F("PM2p5");
+ case 6: return F("PM4p0");
+ case 7: return F("PM10p0");
+ case 8: return F("DewPoint");
+ }
+ return F("");
+}
+
+
+// When using interrupts we have to call the library entry point
+// whenever an interrupt is triggered
+void IRAM_ATTR Plugin_167_interrupt()
+{
+ //addLog(LOG_LEVEL_ERROR, F("********* SEN5X: interrupt apear!"));
+ if (Plugin_167_SEN)
+ {
+ Plugin_167_SEN->checkPin_interrupt();
+ }
+}
+
+
+
+#endif //USES_P167
\ No newline at end of file
diff --git a/src/src/PluginStructs/P167_data_struct.cpp b/src/src/PluginStructs/P167_data_struct.cpp
new file mode 100644
index 0000000000..c9bc7d5a9e
--- /dev/null
+++ b/src/src/PluginStructs/P167_data_struct.cpp
@@ -0,0 +1,1402 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// P167 device class for IKEA Vindstyrka SEN54 temperature , humidity and air quality sensors
+// See datasheet https://sensirion.com/media/documents/6791EFA0/62A1F68F/Sensirion_Datasheet_Environmental_Node_SEN5x.pdf
+// and info about extra request https://sensirion.com/media/documents/2B6FC1F3/6409E74A/PS_AN_Read_RHT_VOC_and_NOx_RAW_signals_D1.pdf
+// Based upon code from Rob Tillaart, Viktor Balint, https://github.com/RobTillaart/SHT2x
+// Rewritten and adapted for ESPeasy by andibaciu
+// 2023-06-20 Initial version by andibaciu
+//////////////////////////////////////////////////////////////////////////////////////////////////
+
+#include "../PluginStructs/P167_data_struct.h"
+#include "../ESPEasyCore/ESPEasyGPIO.h"
+
+#include
+
+ #ifndef CORE_POST_3_0_0
+ #ifdef ESP8266
+ #define IRAM_ATTR ICACHE_RAM_ATTR
+ #endif
+ #endif
+
+#ifdef USES_P167
+
+
+#define P167_START_MEAS 0x0021 // Start measurement command
+#define P167_START_MEAS_RHT_GAS 0x0037 // Start measurement RHT/Gas command
+#define P167_STOP_MEAS 0x0104 // Stop measurement command
+#define P167_READ_DATA_RDY_FLAG 0x0202 // Read Data Ready Flag command
+#define P167_READ_MEAS 0x03C4 // Read measurement command
+#define P167_R_W_TEMP_COMP_PARAM 0x60B2 // Read/Write Temperature Compensation Parameters command
+#define P167_R_W_TWARM_START_PARAM 0x60C6 // Read/Write Warm Start Parameters command
+#define P167_R_W_VOC_ALG_PARAM 0x60D0 // Read/Write VOC Algorithm Tuning Parameters command
+#define P167_R_W_NOX_ALG_PARAM 0x60E1 // Read/Write NOx Algorithm Tuning Parameters command
+#define P167_R_W_RH_T_ACC_Mode 0x60F7 // Read/Write RH/T Acceleration Mode command
+#define P167_R_W_VOC_ALG_STATE 0x6181 // Read/Write VOC Algorithm State command
+#define P167_START_FAN_CLEAN 0x5607 // Start fan cleaning command
+#define P167_R_W_AUTOCLEN_PARAM 0x8004 // Read/Write Autocleaning Interval Parameters command
+#define P167_READ_PROD_NAME 0xD014 // Read Product Name command
+#define P167_READ_SERIAL_NO 0xD033 // Read Serial Number command
+#define P167_READ_FIRM_VER 0xD100 // Read Firmware Version command
+#define P167_READ_DEVICE_STATUS 0xD206 // Read Device Status command
+#define P167_CLEAR_DEVICE_STATUS 0xD210 // Clear Device Status command
+#define P167_RESET_DEVICE 0xD304 // Reset Device command
+#define P167_READ_RAW_MEAS 0x03D2 // Read relative humidity and temperature
+ // which are not compensated for temperature offset, and the
+ // VOC and NOx raw signals (proportional to the logarithm of the
+ // resistance of the MOX layer). It returns 4x2 bytes (+ 1 CRC
+ // byte each) command (see second datasheet fron header for more info)
+#define P167_READ_RAW_MYS_MEAS 0x03F5 // Read relative humidity and temperature and MYSTERY word (probably signed offset temperature)
+
+
+#define P167_START_MEAS_DELAY 50 // Timeout value for start measurement command [ms]
+#define P167_START_MEAS_RHT_GAS_DELAY 50 // Timeout value for start measurement RHT/Gas command [ms]
+#define P167_STOP_MEAS_DELAY 200 // Timeout value for start measurement command [ms]
+#define P167_READ_DATA_RDY_FLAG_DELAY 20 // Timeout value for read data ready flag command [ms]
+#define P167_READ_MEAS_DELAY 20 // Timeout value for read measurement command [ms]
+#define P167_R_W_TEMP_COMP_PARAM_DELAY 20 // Timeout value for read/write temperature compensation parameters command [ms]
+#define P167_R_W_WARM_START_PARAM_DELAY 20 // Timeout value for read/write warm start parameters command [ms]
+#define P167_R_W_VOC_ALG_PARAM_DELAY 20 // Timeout value for read/write VOC algorithm tuning parameters command [ms]
+#define P167_R_W_NOX_ALG_PARAM_DELAY 20 // Timeout value for read/write NOx algorithm tuning parameters command [ms]
+#define P167_R_W_RH_T_ACC_MODE_DELAY 20 // Timeout value for read/write RH/T acceleration mode command [ms]
+#define P167_R_W_VOC_ALG_STATE_DELAY 20 // Timeout value for read/write VOC algorithm State command [ms]
+#define P167_START_FAN_CLEAN_DELAY 20 // Timeout value for start fan cleaning command [ms]
+#define P167_R_W_AUTOCLEN_PARAM_DELAY 20 // Timeout value for read/write autoclean interval parameters command [ms]
+#define P167_READ_PROD_NAME_DELAY 20 // Timeout value for read product name command [ms]
+#define P167_READ_SERIAL_NO_DELAY 20 // Timeout value for read serial number command [ms]
+#define P167_READ_FIRM_VER_DELAY 20 // Timeout value for read firmware version command [ms]
+#define P167_READ_DEVICE_STATUS_DELAY 20 // Timeout value for read device status command [ms]
+#define P167_CLEAR_DEVICE_STATUS_DELAY 20 // Timeout value for clear device status command [ms]
+#define P167_RESET_DEVICE_DELAY 100 // Timeout value for reset device command [ms]
+#define P167_READ_RAW_MEAS_DELAY 20 // Timeout value for read raw temp and humidity command [ms]
+
+
+#define P167_MAX_RETRY 250 // Give up after amount of retries befoe going to error
+
+
+#define SCL_MONITOR_PIN 13 //pin13 as monitor scl i2c
+
+
+//////////////////////////////////////////////////////////////////////////////////////////////////
+//
+// PUBLIC
+//
+P167_data_struct::P167_data_struct()
+{
+ _errCount = 0;
+
+ _Temperature = 0.0;
+ _rawTemperature = 0.0;
+ _mysTemperature = 0.0;
+ _Humidity = 0.0;
+ _rawHumidity = 0.0;
+ _mysHumidity = 0.0;
+ _DewPoint = 0.0;
+
+ _tVOC = 0.0;
+ _rawtVOC = 0.0;
+ _NOx = 0.0;
+ _rawNOx = 0.0;
+ _mysOffset = 0.0;
+ _PM1p0 = 0.0;
+ _PM2p5 = 0.0;
+ _PM4p0 = 0.0;
+ _PM10p0 = 0.0;
+
+ _devicestatus.val = (uint32_t)0;
+
+ _readingerrcode = VIND_ERR_NO_ERROR;
+ _readingerrcount = 0;
+ _readingsuccesscount = 0;
+
+ _model = 0;
+ _i2caddr = 0;
+ _monpin = 0;
+
+ _state = P167_state::Uninitialized;
+ _eid_productname = F("");
+ _eid_serialnumber = F("");
+ _firmware = 0;
+ _last_action_started = 0;
+ _userreg = 0;
+}
+
+
+P167_data_struct::~P167_data_struct()
+{
+ //
+}
+
+// Initialize/setup device properties
+// Must be called at least once before oP167::Wairperating the device
+bool P167_data_struct::setupDevice(uint8_t i2caddr)
+{
+ _i2caddr = i2caddr;
+
+#ifdef PLUGIN_167_DEBUG
+ if (loglevelActiveFor(LOG_LEVEL_INFO))
+ {
+ String log = F("SEN5x: Setup with address= ");
+ log += formatToHex(_i2caddr);
+ addLog(LOG_LEVEL_INFO, log);
+ }
+#endif
+ return true;
+}
+
+
+bool P167_data_struct::setupMonPin(uint8_t monpin)
+{
+ if (validGpio(monpin))
+ {
+ _monpin = monpin;
+ pinMode(_monpin, INPUT_PULLUP); //declare monitoring pin as input with pullup's
+ //attachInterruptArg(digitalPinToInterrupt(_monpin), reinterpret_cast(checkPin), this, CHANGE);
+ //enableInterrupt_monpin();
+
+ #ifdef PLUGIN_167_DEBUG
+ if (loglevelActiveFor(LOG_LEVEL_INFO))
+ {
+ String log = F("SEN5x: Setup I2C SCL monpin= ");
+ log += _monpin;
+ addLog(LOG_LEVEL_INFO, log);
+ }
+ #endif
+ return true;
+ }
+ else
+ return false;
+}
+
+
+void P167_data_struct::enableInterrupt_monpin(void)
+{
+ //attachInterruptArg(digitalPinToInterrupt(_monpin), reinterpret_cast(checkPin), this, CHANGE);
+}
+
+
+void P167_data_struct::disableInterrupt_monpin(void)
+{
+ //detachInterrupt(digitalPinToInterrupt(_monpin));
+}
+
+// Initialize/setup device properties
+// Must be called at least once before oP167::Wairperating the device
+bool P167_data_struct::setupModel(uint8_t model)
+{
+ _model = model;
+
+#ifdef PLUGIN_167_DEBUG
+ if (loglevelActiveFor(LOG_LEVEL_INFO))
+ {
+ String log = F("SEN5x: Setup model= ");
+ log += String(_model);
+ addLog(LOG_LEVEL_INFO, log);
+ }
+#endif
+ return true;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////
+// Evaluate FSM for data acquisition
+// This is a state machine that is evaluated step by step by calling update() repetatively
+// NOTE: Function is expected to run as critical section w.r.t. other provided functions
+// This is typically met in ESPeasy plugin context when called from within the plugin
+bool P167_data_struct::update()
+{
+ bool stable = false; // signals when a stable state is reached
+#ifdef PLUGIN_167_DEBUG
+ P167_state oldState = _state;
+#endif
+
+
+ if(statusMonitoring == false)
+ return stable;
+
+
+ switch(_state)
+ {
+ case P167_state::Uninitialized:
+ //we have to stop trying after a while
+ if (_errCount>P167_MAX_RETRY)
+ {
+ _state = P167_state::Error;
+ stable = true;
+ }
+ else if (I2C_wakeup(_i2caddr) != 0) // Try to access the I2C device
+ {
+ if (loglevelActiveFor(LOG_LEVEL_ERROR))
+ {
+ String log = F("SEN5x : Not found at I2C address: ");
+ log += String(_i2caddr, HEX);
+ addLog(LOG_LEVEL_ERROR, log);
+ }
+ _errCount++;
+ }
+ else if (_model==0 ) //sensor is Vindstyrka and d'ont need to be reset
+ {
+ _errCount = 0; // Device is reachable and initialized, reset error counter
+ if (writeCmd(P167_READ_FIRM_VER)) // Issue a reset command
+ {
+ _state = P167_state::Read_firm_version; // Will take <20ms according to datasheet
+ _last_action_started = millis();
+ }
+ }
+ else if (_model==1)
+ {
+ _errCount = 0; // Device is reachable and initialized, reset error counter
+ if (writeCmd(P167_RESET_DEVICE)) // Issue a reset command
+ {
+ _state = P167_state::Wait_for_reset; // Will take <20ms according to datasheet
+ _last_action_started = millis();
+ }
+ }
+ break;
+
+ case P167_state::Wait_for_reset:
+ if (timeOutReached(_last_action_started + P167_RESET_DEVICE_DELAY)) //we need to wait for the chip to reset
+ {
+ if (I2C_wakeup(_i2caddr) != 0)
+ {
+ _errCount++;
+ _state = P167_state::Uninitialized; // Retry
+ }
+ else
+ {
+ _errCount = 0; // Device is reachable and initialized, reset error counter
+ if (writeCmd(P167_READ_FIRM_VER))
+ {
+ _state = P167_state::Read_firm_version; // Will take <20ms according to datasheet
+ _last_action_started = millis();
+ }
+ }
+ }
+ break;
+
+ case P167_state::Read_firm_version:
+ if (timeOutReached(_last_action_started + P167_READ_FIRM_VER_DELAY))
+ {
+ // Start read flag
+ if (!getFirmwareVersion())
+ {
+ _errCount++;
+ _state = P167_state::Uninitialized; // Retry
+ }
+ else if(!writeCmd(P167_READ_PROD_NAME))
+ {
+ _errCount++;
+ _state = P167_state::Uninitialized; // Retry
+ }
+ else
+ {
+ _last_action_started = millis();
+ _state = P167_state::Read_prod_name;
+ }
+ }
+ break;
+
+ case P167_state::Read_prod_name:
+ if (timeOutReached(_last_action_started + P167_READ_PROD_NAME_DELAY))
+ {
+ // Start read flag
+ if (!getProductName())
+ {
+ _errCount++;
+ _state = P167_state::Uninitialized; // Retry
+ }
+ else if(!writeCmd(P167_READ_SERIAL_NO))
+ {
+ _errCount++;
+ _state = P167_state::Uninitialized; // Retry
+ }
+ else
+ {
+ _last_action_started = millis();
+ _state = P167_state::Read_serial_no;
+ }
+ }
+ break;
+
+ case P167_state::Read_serial_no:
+ if (timeOutReached(_last_action_started + P167_READ_SERIAL_NO_DELAY))
+ {
+ // Start read flag
+ if (!getSerialNumber())
+ {
+ _errCount++;
+ _state = P167_state::Uninitialized; // Retry
+ }
+ else if(!writeCmd(P167_READ_SERIAL_NO))
+ {
+ _errCount++;
+ _state = P167_state::Uninitialized; // Retry
+ }
+ else
+ {
+ _last_action_started = millis();
+ _state = P167_state::Initialized;
+ }
+ }
+ break;
+
+ case P167_state::Write_user_reg:
+ _state = P167_state::Initialized;
+ break;
+
+ case P167_state::Initialized:
+ // For now trigger the first read cycle automatically
+ //_state = P167_state::Ready;
+ break;
+
+ case P167_state::Ready:
+ // Ready to execute a measurement cycle
+ if( _model==0 || _model==1 || _model==2)
+ {
+ // Start measuring data
+ if (!writeCmd(P167_START_MEAS))
+ {
+ _errCount++;
+ _state = P167_state::Uninitialized; // Retry
+ }
+ else
+ {
+ _last_action_started = millis();
+ _state = P167_state::Wait_for_start_meas;
+ }
+ }
+ break;
+
+ case P167_state::Wait_for_start_meas:
+ if (timeOutReached(_last_action_started + P167_START_MEAS_DELAY))
+ {
+ // Start read flag
+ if (!writeCmd(P167_READ_DATA_RDY_FLAG))
+ {
+ _errCount++;
+ _state = P167_state::Uninitialized; // Retry
+ }
+ else
+ {
+ _last_action_started = millis();
+ _state = P167_state::Wait_for_read_flag;
+ }
+ }
+ break;
+
+ case P167_state::Wait_for_read_flag:
+ if (timeOutReached(_last_action_started + P167_READ_DATA_RDY_FLAG_DELAY))
+ {
+ if(readDataRdyFlag())
+ {
+ // Ready to execute a measurement cycle
+ if (!writeCmd(P167_READ_MEAS))
+ {
+ _errCount++;
+ _state = P167_state::Uninitialized; // Retry
+ }
+ else
+ {
+ _last_action_started = millis();
+ _state = P167_state::Wait_for_read_meas;
+ }
+ }
+ else //Ready Flag NOT ok, so send again Start Measurement
+ {
+ // Start measuring data
+ if (!writeCmd(P167_START_MEAS))
+ {
+ _errCount++;
+ _state = P167_state::Uninitialized; // Retry
+ }
+ else
+ {
+ _last_action_started = millis();
+ _state = P167_state::Wait_for_start_meas;
+ }
+ }
+ }
+ break;
+
+ case P167_state::Wait_for_read_meas:
+ if (timeOutReached(_last_action_started + P167_READ_MEAS_DELAY))
+ {
+ if (!readMeasValue()) // Read the previously measured temperature
+ {
+ _errCount++;
+ //_state = P167_state::Uninitialized; // Lost connection
+ _state = P167_state::cmdSTARTmeas;
+ }
+ else
+ {
+ if (!writeCmd(P167_READ_RAW_MEAS))
+ {
+ _errCount++;
+ _state = P167_state::Uninitialized; // Retry
+ }
+ else
+ {
+ _last_action_started = millis();
+ _state = P167_state::Wait_for_read_raw_meas;
+ }
+ }
+ }
+ break;
+
+ case P167_state::Wait_for_read_raw_meas:
+ //make sure we wait for the measurement to complete
+ if (timeOutReached(_last_action_started + P167_READ_RAW_MEAS_DELAY))
+ {
+ if (!readMeasRawValue())
+ {
+ _errCount++;
+ //_state = P167_state::Uninitialized; // Lost connection
+ _state = P167_state::cmdSTARTmeas;
+ }
+ else
+ {
+ if (!writeCmd(P167_READ_RAW_MYS_MEAS))
+ {
+ _errCount++;
+ _state = P167_state::Uninitialized; // Retry
+ }
+ else
+ {
+ _last_action_started = millis();
+ _state = P167_state::Wait_for_read_raw_MYS_meas;
+ }
+ }
+ }
+ break;
+
+ case P167_state::Wait_for_read_raw_MYS_meas:
+ //make sure we wait for the measurement to complete
+ if (timeOutReached(_last_action_started + P167_READ_RAW_MEAS_DELAY))
+ {
+ if (!readMeasRawMYSValue())
+ {
+ _errCount++;
+ //_state = P167_state::Uninitialized; // Lost connection
+ _state = P167_state::cmdSTARTmeas;
+ }
+ else
+ {
+ if (!writeCmd(P167_READ_DEVICE_STATUS))
+ {
+ _errCount++;
+ _state = P167_state::Uninitialized; // Retry
+ }
+ else
+ {
+ _last_action_started = millis();
+ _state = P167_state::Wait_for_read_status;
+ }
+ calculateValue();
+ stable = true;
+ }
+ }
+ break;
+
+ case P167_state::Wait_for_read_status:
+ //make sure we wait for the measurement to complete
+ if (timeOutReached(_last_action_started + P167_READ_DEVICE_STATUS_DELAY))
+ {
+ if (!readDeviceStatus())
+ {
+ _errCount++;
+ //_state = P167_state::Uninitialized; // Lost connection
+ _state = P167_state::cmdSTARTmeas;
+ }
+ else
+ {
+ _last_action_started = millis();
+ _state = P167_state::cmdSTARTmeas;
+ stable = true;
+ }
+ }
+ break;
+
+ case P167_state::cmdSTARTmeas:
+ // Start measuring data
+ if(_model==0)
+ {
+ if (!writeCmd(P167_START_MEAS))
+ {
+ _errCount++;
+ _state = P167_state::Uninitialized; // Retry
+ }
+ else
+ {
+ _last_action_started = millis();
+ _state = P167_state::IDLE;
+ }
+ }
+ else
+ {
+ _state = P167_state::IDLE;
+ }
+ break;
+
+ case P167_state::IDLE:
+ stepMonitoring = 1;
+ startMonitoringFlag = false;
+ if(!_errmeas && !_errmeasraw && !_errmeasrawmys)
+ _state = P167_state::New_Values_Available;
+ stable = true;
+ break;
+
+ case P167_state::Error:
+ case P167_state::New_Values_Available:
+ //this state is used outside so all we need is to stay here
+ stable = true;
+ break;
+
+ //Missing states (enum values) to be checked by the compiler
+
+ } // switch
+
+#ifdef PLUGIN_167_DEBUG
+ if (_state != oldState)
+ {
+ if (loglevelActiveFor(LOG_LEVEL_INFO))
+ {
+ String log = F("SEN5x : *** state transition ");
+ log += String((int)oldState);
+ log += F("-->");
+ log += String((int)_state);
+ addLog(LOG_LEVEL_INFO, log);
+ }
+ }
+#endif
+ return stable;
+}
+
+
+bool P167_data_struct::monitorSCL()
+{
+ if(_model==0)
+ {
+ if(startMonitoringFlag)
+ {
+
+ if(stepMonitoring==1)
+ {
+ lastSCLLowTransitionMonitoringTime = monpinLastTransitionTime/1000;
+ if(millis() - lastSCLLowTransitionMonitoringTime < 100)
+ {
+ statusMonitoring = false;
+ return true;
+ }
+ else
+ {
+ lastSCLLowTransitionMonitoringTime = monpinLastTransitionTime/1000;
+ statusMonitoring = true;
+ stepMonitoring++;
+ }
+ }
+
+ if(stepMonitoring==2)
+ {
+ if(millis() - lastSCLLowTransitionMonitoringTime < 100)
+ {
+ lastSCLLowTransitionMonitoringTime = monpinLastTransitionTime/1000;
+ statusMonitoring = false;
+ stepMonitoring = 1;
+ return true;
+ }
+ else if(millis() - lastSCLLowTransitionMonitoringTime > 700)
+ {
+ statusMonitoring = false;
+ stepMonitoring = 1;
+ startMonitoringFlag = false;
+
+ //if _state not finish reading process then start from begining
+ if(_state >= P167_state::Wait_for_read_meas && _state < P167_state::New_Values_Available)
+ {
+ _state = P167_state::Ready;
+ }
+ return true;
+ }
+ else
+ {
+ //processing
+ }
+ }
+ }
+ monpinValuelast = monpinValue;
+ }
+
+ if(_model == 1 || _model == 2)
+ {
+ statusMonitoring = true;
+ startMonitoringFlag = false;
+ stepMonitoring = 0;
+ }
+
+ return true;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////
+// Returns the I2C connection state
+// Note: based upon the FSM state without actual accessing the device
+bool P167_data_struct::isConnected() const
+{
+ switch (_state)
+ {
+ case P167_state::Initialized:
+ case P167_state::Ready:
+ case P167_state::Wait_for_start_meas:
+ case P167_state::Wait_for_read_flag:
+ case P167_state::Wait_for_read_meas:
+ case P167_state::Wait_for_read_raw_meas:
+ case P167_state::Wait_for_read_raw_MYS_meas:
+ case P167_state::Wait_for_read_status:
+ case P167_state::cmdSTARTmeas:
+ case P167_state::New_Values_Available:
+ case P167_state::Read_firm_version:
+ case P167_state::Read_prod_name:
+ case P167_state::Read_serial_no:
+ case P167_state::Write_user_reg:
+ case P167_state::IDLE:
+ return true;
+ break;
+ case P167_state::Uninitialized:
+ case P167_state::Error:
+ case P167_state::Wait_for_reset:
+ return false;
+ break;
+
+ //Missing states (enum values) to be checked by the compiler
+ }
+ return false;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////
+// Returns if the device communication is in error
+// Note: based upon the FSM state without actual accessing the device
+bool P167_data_struct::inError() const
+{
+ return _state == P167_state::Error;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////
+// Returns if new acquired values are available
+bool P167_data_struct::newValues() const
+{
+ return _state == P167_state::New_Values_Available;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////
+// Restart the FSM used to access the device
+bool P167_data_struct::reset()
+{
+ startMonitoringFlag = true;
+ stepMonitoring = 1;
+ _state = P167_state::Uninitialized;
+ return true;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////
+// Start a new measurement cycle
+bool P167_data_struct::startMeasurements()
+{
+ if ((_state == P167_state::New_Values_Available) || (_state == P167_state::Initialized) || (_state == P167_state::IDLE))
+ {
+ _state = P167_state::Ready;
+ }
+ startMonitoringFlag = true;
+ stepMonitoring = 1;
+ return true;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////
+// Get the electronic idenfification data store in the device
+// Note: The data is read from the device during initialization
+bool P167_data_struct::getEID(String &eid_productname, String &eid_serialnumber, uint8_t &firmware) const
+{
+ eid_productname = _eid_productname;
+ eid_serialnumber = _eid_serialnumber;
+ firmware = _firmware;
+ return true;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////
+// Get the status informasion about different part of the sensor
+// Note: The data is read from the device after every measurement read request
+bool P167_data_struct::getStatusInfo(param_statusinfo param)
+{
+ switch(param)
+ {
+ case sensor_speed:
+ return (bool) _devicestatus.speed;
+ break;
+
+ case sensor_autoclean:
+ return (bool) _devicestatus.autoclean;
+ break;
+
+ case sensor_gas:
+ return (bool) _devicestatus.gas;
+ break;
+
+ case sensor_rht:
+ return (bool) _devicestatus.rht;
+ break;
+
+ case sensor_laser:
+ return (bool) _devicestatus.laser;
+ break;
+
+ case sensor_fan:
+ return (bool) _devicestatus.fan;
+ break;
+ }
+ return true;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////
+// Return the previously measured raw humidity data [bits]
+float P167_data_struct::getRequestedValue(uint8_t request) const
+{
+ //float requested_value=0;
+ switch(request)
+ {
+ case 0:
+ {
+ if(_model==0)
+ return (float) _TemperatureX;
+ else
+ return (float) _Temperature;
+ }
+ case 1:
+ {
+ if(_model==0)
+ return (float) _HumidityX;
+ else
+ return (float) _Humidity;
+ }
+ case 2: return (float) _tVOC;
+ case 3: return (float) _NOx;
+ case 4: return (float) _PM1p0;
+ case 5: return (float) _PM2p5;
+ case 6: return (float) _PM4p0;
+ case 7: return (float) _PM10p0;
+ case 8: return (float) _DewPoint;
+ }
+ return -1;
+}
+
+
+//////////////////////////////////////////////////////////////////////////////////////////////////
+//
+// PROTECTED
+//
+//////////////////////////////////////////////////////////////////////////////////////////////////
+
+//////////////////////////////////////////////////////////////////////////////////////////////////
+uint8_t P167_data_struct::crc8(const uint8_t *data, uint8_t len)
+{
+ // CRC-8 formula from page 14 of SHT spec pdf
+ // Sensirion_Humidity_Sensors_SHT2x_CRC_Calculation.pdf
+ const uint8_t POLY = 0x31;
+ uint8_t crc = 0xFF;
+
+ for (uint8_t j = 0; j> 8));
+ Wire.write((uint8_t)cmd);
+ return Wire.endTransmission() == 0;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////
+bool P167_data_struct::writeCmd(uint16_t cmd, uint8_t value)
+{
+ Wire.beginTransmission(_i2caddr);
+ Wire.write((uint8_t)(cmd >> 8));
+ Wire.write((uint8_t)cmd);
+ Wire.write((uint8_t)value);
+ return Wire.endTransmission() == 0;
+}
+
+
+
+bool P167_data_struct::writeCmd(uint16_t cmd, uint8_t length, uint8_t *buffer)
+{
+ Wire.beginTransmission(_i2caddr);
+ Wire.write((uint8_t)(cmd >> 8));
+ Wire.write((uint8_t)cmd);
+ for (int i = 0; i < length; i++) {
+ Wire.write(*(buffer + i));
+ }
+ return Wire.endTransmission() == 0;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////
+bool P167_data_struct::readBytes(uint8_t n, uint8_t *val, uint8_t maxDuration)
+{
+ // TODO check if part can be delegated to the I2C_access libraray from ESPeasy
+ Wire.requestFrom(_i2caddr, (uint8_t) n);
+ uint32_t start = millis();
+ while (Wire.available() < n)
+ {
+ if (timePassedSince(start) > maxDuration)
+ {
+ return false;
+ }
+ yield();
+ }
+
+ for (uint8_t i = 0; i < n; i++)
+ {
+ val[i] = Wire.read();
+ }
+ return true;
+}
+
+
+//////////////////////////////////////////////////////////////////////////////////////////////////
+// Read data ready flag from device
+bool P167_data_struct::readDataRdyFlag()
+{
+ uint8_t value=0;
+ uint8_t buffer[3];
+
+ if (!readBytes(3, (uint8_t*) &buffer[0], P167_READ_DATA_RDY_FLAG_DELAY))
+ {
+ return false;
+ }
+ if (crc8(&buffer[0], 2) == buffer[2])
+ {
+ value += buffer[1];
+ }
+ return value;
+}
+
+
+//////////////////////////////////////////////////////////////////////////////////////////////////
+// Read measurement values results from device
+bool P167_data_struct::readMeasValue()
+{
+ uint16_t value=0;
+ int16_t valuesign=0;
+ uint8_t buffer[24];
+ bool condition=true;
+
+ _errmeas = false;
+ if (!readBytes(24, (uint8_t*) &buffer[0], P167_READ_MEAS_DELAY))
+ {
+ _errmeas = true;
+ return false;
+ }
+
+ String log = F("SEN5x : *** meas value ");
+ for(int xx=0; xx<24;xx++)
+ {
+ log += String((int)buffer[xx]);
+ log += F(" ");
+ }
+
+ if(_model==0 || _model==1)
+ condition=(buffer[0] == 0xFF && buffer[1] == 0xFF) || (buffer[3] == 0xFF && buffer[4] == 0xFF) || (buffer[6] == 0xFF && buffer[7] == 0xFF) || (buffer[9] == 0xFF && buffer[10] == 0xFF) || (buffer[12] == 0xFF && buffer[13] == 0xFF) || (buffer[15] == 0xFF && buffer[16] == 0xFF) || (buffer[18] == 0xFF && buffer[19] == 0xFF);
+ if(_model==2)
+ condition=(buffer[0] == 0xFF && buffer[1] == 0xFF) || (buffer[3] == 0xFF && buffer[4] == 0xFF) || (buffer[6] == 0xFF && buffer[7] == 0xFF) || (buffer[9] == 0xFF && buffer[10] == 0xFF) || (buffer[12] == 0xFF && buffer[13] == 0xFF) || (buffer[15] == 0xFF && buffer[16] == 0xFF) || (buffer[18] == 0xFF && buffer[19] == 0xFF) || (buffer[21] == 0xFF && buffer[22] == 0xFF);
+
+ if(condition)
+ {
+ log += F("- error");
+ addLog(LOG_LEVEL_INFO, log);
+ _errmeas = true;
+ _readingerrcount++;
+ return false;
+ }
+ else
+ {
+ for(int xx=0; xx<8; xx++)
+ {
+ if ((crc8(&buffer[xx*3], 2) == buffer[xx*3+2]) && (buffer[xx*3] != 0xFF || buffer[xx*3+1] != 0xFF))
+ {
+ value = buffer[xx*3] << 8;
+ value += buffer[xx*3+1];
+ valuesign = buffer[xx*3] << 8;
+ valuesign += buffer[xx*3+1];
+ if(xx==0)
+ _PM1p0 = (float)value/10;
+ if(xx==1)
+ _PM2p5 = (float)value/10;
+ if(xx==2)
+ _PM4p0 = (float)value/10;
+ if(xx==3)
+ _PM10p0 = (float)value/10;
+ if(xx==4)
+ _Humidity = (float)valuesign/100.0;
+ if(xx==5)
+ _Temperature = (float)valuesign/200.0;
+ if(xx==6)
+ _tVOC = (float)valuesign/10.0;
+ if(xx==7)
+ {
+ if(_model==2)
+ _NOx = (float)valuesign/10.0;
+ else
+ _NOx = (float)0.0;
+ }
+ }
+ else
+ {
+ _errmeasrawmys = true;
+ }
+ }
+
+ if(_errmeas == true)
+ {
+ log += F("- crc error");
+ _readingerrcount++;
+ }
+ else
+ {
+ log += F("- pass");
+ _readingsuccesscount++;
+ }
+ addLog(LOG_LEVEL_INFO, log);
+ return !_errmeas;
+ }
+
+ return true;
+}
+
+
+//////////////////////////////////////////////////////////////////////////////////////////////////
+// Read measurement values results from device
+bool P167_data_struct::readMeasRawValue()
+{
+ uint16_t value=0;
+ int16_t valuesign=0;
+ uint8_t buffer[12];
+ bool condition=true;
+
+ _errmeasraw = false;
+ if (!readBytes(12, (uint8_t*) &buffer[0], P167_READ_RAW_MEAS_DELAY))
+ {
+ _errmeasraw = true;
+ return false;
+ }
+
+ String log = F("SEN5x : *** meas RAW value ");
+ for(int xx=0; xx<12;xx++)
+ {
+ log += String((int)buffer[xx]);
+ log += F(" ");
+ }
+
+ if(_model==0 || _model==1)
+ condition=(buffer[0] == 0xFF && buffer[1] == 0xFF) || (buffer[3] == 0xFF && buffer[4] == 0xFF) || (buffer[6] == 0xFF && buffer[7] == 0xFF);// || (buffer[9] == 0xFF && buffer[10] == 0xFF))
+ if(_model==2)
+ condition=(buffer[0] == 0xFF && buffer[1] == 0xFF) || (buffer[3] == 0xFF && buffer[4] == 0xFF) || (buffer[6] == 0xFF && buffer[7] == 0xFF) || (buffer[9] == 0xFF && buffer[10] == 0xFF);
+
+ if(condition)
+ {
+ log += F("- error");
+ addLog(LOG_LEVEL_INFO, log);
+ _errmeasraw = true;
+ _readingerrcount++;
+ return false;
+ }
+ else
+ {
+ for(int xx=0; xx<4; xx++)
+ {
+ if ((crc8(&buffer[xx*3], 2) == buffer[xx*3+2]) && (buffer[xx*3] != 0xFF || buffer[xx*3+1] != 0xFF))
+ {
+ value = buffer[xx*3] << 8;
+ value += buffer[xx*3+1];
+ valuesign = buffer[xx*3] << 8;
+ valuesign += buffer[xx*3+1];
+ if(xx==0)
+ _rawHumidity = (float)valuesign/100.0;
+ if(xx==1)
+ _rawTemperature = (float)valuesign/200.0;
+ if(xx==2)
+ _rawtVOC = (float)value/10.0;
+ if(xx==3)
+ {
+ if(_model==2)
+ _rawNOx = (float)value/10.0;
+ else
+ _rawNOx = (float)0.0;
+ }
+ }
+ else
+ {
+ _errmeasrawmys = true;
+ }
+ }
+
+ if(_errmeasraw == true)
+ {
+ log += F("- crc error");
+ _readingerrcount++;
+ }
+ else
+ {
+ log += F("- pass");
+ _readingsuccesscount++;
+ }
+ addLog(LOG_LEVEL_INFO, log);
+ return !_errmeasraw;
+ }
+
+
+ return true;
+}
+
+
+
+//////////////////////////////////////////////////////////////////////////////////////////////////
+// Read measurement values results from device
+bool P167_data_struct::readMeasRawMYSValue()
+{
+ uint16_t value=0;
+ int16_t valuesign=0;
+ uint8_t buffer[9];
+
+ _errmeasrawmys = false;
+ if (!readBytes(9, (uint8_t*) &buffer[0], P167_READ_RAW_MEAS_DELAY))
+ {
+ _errmeasrawmys = true;
+ return false;
+ }
+
+ String log = F("SEN5x : *** meas MYS value ");
+ for(int xx=0; xx<9;xx++)
+ {
+ log += String((int)buffer[xx]);
+ log += F(" ");
+ }
+
+ if((buffer[0] == 0xFF && buffer[1] == 0xFF) || (buffer[3] == 0xFF && buffer[4] == 0xFF) || (buffer[6] == 0xFF && buffer[7] == 0xFF))
+ {
+ log += F("- error");
+ addLog(LOG_LEVEL_INFO, log);
+ _errmeasrawmys = true;
+ _readingerrcount++;
+ return false;
+ }
+ else
+ {
+ for(int xx=0; xx<3; xx++)
+ {
+ if ((crc8(&buffer[xx*3], 2) == buffer[xx*3+2]) && (buffer[xx*3] != 0xFF || buffer[xx*3+1] != 0xFF))
+ {
+ value = buffer[xx*3] << 8;
+ value += buffer[xx*3+1];
+ valuesign = buffer[xx*3] << 8;
+ valuesign += buffer[xx*3+1];
+ if(xx==0)
+ _mysHumidity = (float)valuesign/100.0;
+ if(xx==1)
+ _mysTemperature = (float)valuesign/200.0;
+ if(xx==2)
+ _mysOffset = (float)valuesign/200.0;
+ }
+ else
+ {
+ _errmeasrawmys = true;
+ }
+ }
+
+ if(_errmeasrawmys == true)
+ {
+ log += F("- crc error");
+ _readingerrcount++;
+ }
+ else
+ {
+ log += F("- pass");
+ _readingsuccesscount++;
+ }
+ addLog(LOG_LEVEL_INFO, log);
+ return !_errmeasrawmys;
+ }
+
+
+ return true;
+}
+
+
+//////////////////////////////////////////////////////////////////////////////////////////////////
+// Calculate DewPoint, F Temp, F HUM
+bool P167_data_struct::calculateValue()
+{
+ float lnval;
+ float rapval;
+ float aval = 17.62;
+ float bval = 243.12;
+ float Dp;
+ float eeval = 0.0;
+
+ if(_model==0)
+ {
+ //_TemperatureX = _mysTemperature + _mysOffset - (_mysOffset<0.0?(_mysOffset*(-1.0)):_mysOffset)/2;
+ //_TemperatureX = _mysTemperature + _mysOffset*3.0/2.0;
+ _TemperatureX = _mysTemperature + _mysOffset - 2.4; //(2.4 - temperature offset because enclosure and esp8266 power disipation)
+
+ //version formula with DewPoint
+ //lnval = logf(_mysHumidity/100.0);
+ //rapval = (aval * _mysTemperature)/(bval+_mysTemperature);
+ //Dp = (bval*(lnval+rapval))/(aval-lnval-rapval);
+ //eeval = expf((aval*Dp)/(bval+Dp))/expf((aval*_TemperatureX)/(bval+_TemperatureX));
+ //_HumidityX = eeval*100.0;
+
+ //version formula with interpolation
+ _HumidityX = _Humidity+(_TemperatureX-_Temperature)*((_rawHumidity-_Humidity)/(_rawTemperature-_Temperature));
+ lnval = logf(_HumidityX/100.0);
+ rapval = (aval * _TemperatureX)/(bval+_TemperatureX);
+ Dp = (bval*(lnval+rapval))/(aval-lnval-rapval);
+
+ if(_HumidityX < 0.0)
+ _HumidityX = 0.0;
+ if(_HumidityX > 100.0)
+ _HumidityX = 100.0;
+ }
+ else
+ {
+ lnval = logf(_Humidity/100.0);
+ rapval = (aval * _Temperature)/(bval+_Temperature);
+ Dp = (bval*(lnval+rapval))/(aval-lnval-rapval);
+ }
+ _DewPoint = Dp;
+
+ return true;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////
+// Retrieve SEN5x identification code
+// Sensirion_SEN5x
+bool P167_data_struct::getProductName()
+{
+ String prodname=F("");
+ uint8_t buffer[48];
+ //writeCmd(P167_READ_PROD_NAME);
+ if (!readBytes(48, (uint8_t *) buffer, P167_READ_PROD_NAME_DELAY))
+ {
+ return false;
+ }
+ for (uint8_t i = 1; i <= 16; i++)
+ {
+ if(crc8(&buffer[i*3-3], 2) == buffer[i*3-1])
+ {
+ if (buffer[i*3-3] < 32)
+ break;
+ prodname+=char(buffer[i*3-3]);
+ if (buffer[i*3-2] < 32)
+ break;
+ prodname+=char(buffer[i*3-2]);
+ }
+ }
+ _eid_productname=prodname;
+
+ String log = F("SEN5x : *** Product name: ");
+ log += String(prodname);
+ addLog(LOG_LEVEL_INFO, log);
+
+ return true;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////
+// Retrieve SEN54 Serial Number
+bool P167_data_struct::getSerialNumber()
+{
+ String serno=F("");
+ uint8_t buffer[48];
+ //writeCmd(P167_READ_SERIAL_NO);
+ if (!readBytes(48, (uint8_t *) buffer, P167_READ_SERIAL_NO_DELAY))
+ {
+ return false;
+ }
+ for (uint8_t i = 1; i <= 16; i++)
+ {
+ if(crc8(&buffer[i*3-3], 2) == buffer[i*3-1])
+ {
+ if (buffer[i*3-3] < 32)
+ break;
+ serno+=char(buffer[i*3-3]);
+ if (buffer[i*3-2] < 32)
+ break;
+ serno+=char(buffer[i*3-2]);
+ }
+ }
+ _eid_serialnumber=serno;
+
+ String log = F("SEN5x : *** Serial number: ");
+ log += String(serno);
+ addLog(LOG_LEVEL_INFO, log);
+
+ return true;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////
+// Retrieve SEN54 Firmware version from device
+bool P167_data_struct::getFirmwareVersion()
+{
+ uint8_t version = 0;
+ uint8_t read_data[3];
+ //writeCmd(P167_READ_FIRM_VER);
+ if (!readBytes(3, (uint8_t *) &read_data, P167_READ_FIRM_VER_DELAY))
+ {
+ return false;
+ }
+ if( read_data[2] == crc8(&read_data[0],2) )
+ {
+ version=read_data[0];
+ }
+ else
+ {
+ version=0;
+ }
+ _firmware=version;
+
+ String log = F("SEN5x : *** Firmware version: ");
+ log += String((uint8_t)version);
+ addLog(LOG_LEVEL_INFO, log);
+
+ return true;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////
+// Retrieve SEN54 Device Status from device
+bool P167_data_struct::readDeviceStatus()
+{
+ uint32_t value=0;
+ uint8_t bufferstatus[6];
+ //writeCmd(P167_READ_FIRM_VER);
+ _errdevicestatus = false;
+ if (!readBytes(6, (uint8_t *) &bufferstatus, P167_READ_DEVICE_STATUS_DELAY))
+ {
+ _errdevicestatus = true;
+ return false;
+ }
+
+ String log = F("SEN5x : *** device status ");
+ for(int xx=0; xx<6;xx++)
+ {
+ log += String((int)bufferstatus[xx]);
+ log += F(" ");
+ }
+
+ if ((crc8(&bufferstatus[0], 2) == bufferstatus[2]) && (crc8(&bufferstatus[3], 2) == bufferstatus[5]))
+ {
+ value = bufferstatus[0] << 8;
+ value += bufferstatus[1] << 8;
+ value += bufferstatus[3] << 8;
+ value += bufferstatus[4] << 8;
+ _devicestatus.val = value;
+ }
+ else
+ {
+ _errdevicestatus = true;
+ }
+
+ if(_errdevicestatus == true)
+ {
+ log += F("- crc error");
+ _readingerrcount++;
+ }
+ else
+ {
+ log += String((bool)_devicestatus.speed);
+ log += String((bool)_devicestatus.autoclean);
+ log += String((bool)_devicestatus.gas);
+ log += String((bool)_devicestatus.rht);
+ log += String((bool)_devicestatus.laser);
+ log += String((bool)_devicestatus.fan);
+ log += F(" - pass");
+ _readingsuccesscount++;
+ }
+ addLog(LOG_LEVEL_INFO, log);
+ return !_errdevicestatus;
+}
+
+
+uint16_t P167_data_struct::getErrCode(bool _clear)
+{
+ uint16_t _tmp = _readingerrcode;
+ if (_clear == true)
+ clearErrCode();
+ return (_tmp);
+}
+
+uint16_t P167_data_struct::getErrCount(bool _clear)
+{
+ uint16_t _tmp = _readingerrcount;
+ if (_clear == true)
+ clearErrCount();
+ return (_tmp);
+}
+
+uint16_t P167_data_struct::getSuccCount(bool _clear)
+{
+ uint16_t _tmp = _readingsuccesscount;
+ if (_clear == true)
+ clearSuccCount();
+ return (_tmp);
+}
+
+void P167_data_struct::clearErrCode()
+{
+ _readingerrcode = VIND_ERR_NO_ERROR;
+}
+
+void P167_data_struct::clearErrCount()
+{
+ _readingerrcount = 0;
+}
+
+void P167_data_struct::clearSuccCount()
+{
+ _readingsuccesscount = 0;
+}
+
+
+void IRAM_ATTR P167_data_struct::checkPin_interrupt()
+{
+ //ISR_noInterrupts(); // s0170071: avoid nested interrups due to bouncing.
+
+ monpinValue++;
+ monpinLastTransitionTime = getMicros64();
+ // Mark pin value changed
+ monpinChanged = false;
+ if(monpinValue!=monpinValuelast)
+ monpinChanged = true;
+
+ //ISR_interrupts(); // enable interrupts again.
+}
+
+#endif // USES_P167
\ No newline at end of file
diff --git a/src/src/PluginStructs/P167_data_struct.h b/src/src/PluginStructs/P167_data_struct.h
new file mode 100644
index 0000000000..1686b351b5
--- /dev/null
+++ b/src/src/PluginStructs/P167_data_struct.h
@@ -0,0 +1,222 @@
+//////////////////////////////////////////////////////////////////////////////////////////////////
+// P167 device class for IKEA Vindstyrka SEN54 temperature , humidity and air quality sensors
+// See datasheet https://sensirion.com/media/documents/6791EFA0/62A1F68F/Sensirion_Datasheet_Environmental_Node_SEN5x.pdf
+// and info about extra request https://sensirion.com/media/documents/2B6FC1F3/6409E74A/PS_AN_Read_RHT_VOC_and_NOx_RAW_signals_D1.pdf
+// Based upon code from Rob Tillaart, Viktor Balint, https://github.com/RobTillaart/SHT2x
+// Rewritten and adapted for ESPeasy by andibaciu
+// 2023-06-20 Initial version by andibaciu
+//////////////////////////////////////////////////////////////////////////////////////////////////
+
+#include "../../_Plugin_Helper.h"
+#include "../ESPEasyCore/ESPEasyGPIO.h"
+#ifdef USES_P167
+
+#ifdef LIMIT_BUILD_SIZE
+#define PLUGIN_167_DEBUG false
+#else
+#define PLUGIN_167_DEBUG false // set to true for extra log info in the debug
+#endif
+
+// Vindstyrka device properties
+//#define P167_I2C_ADDRESS_DFLT 0x69
+
+
+//------------------------------------------------------------------------------
+#define VIND_ERR_NO_ERROR 0 // no error
+#define VIND_ERR_CRC_ERROR 1 // crc error
+#define VIND_ERR_WRONG_BYTES 2 // bytes b0,b1 or b2 wrong
+#define VIND_ERR_NOT_ENOUGHT_BYTES 3 // not enough bytes from sdm
+#define VIND_ERR_TIMEOUT 4 // timeout
+//------------------------------------------------------------------------------
+
+//////////////////////////////////////////////////////////////////////////////////////////////////
+// Access to the Vindstyrka device is mainly by sequencing a Finate State Machine
+enum class P167_state {
+ Uninitialized = 0, // Initial state, unknown status of sensor device
+ Wait_for_reset, // Reset being performed
+ Read_firm_version, // Reading firmware version
+ Read_prod_name, // Reading production
+ Read_serial_no, // Reading serial number
+ Write_user_reg, // Write the user register
+ Initialized, // Initialization completed
+ Ready, // Aquisition request is pending, ready to measure
+ Wait_for_start_meas, // Start measurement started
+ Wait_for_read_flag, // Read meas flag started
+ Wait_for_read_meas, // Read meas started
+ Wait_for_read_raw_meas, // RAW Read meas started
+ Wait_for_read_raw_MYS_meas, // RAW Read meas MYSTERY started
+ Wait_for_read_status, // Read status
+ cmdSTARTmeas, // send command START meas to leave SEN5x ready flag for Vindstyrka
+ IDLE, // Sensor device in IDLE mode
+ New_Values_Available, // Acqusition finished, new data available
+ Error // Sensor device cannot be accessed or in error
+};
+
+
+enum param_statusinfo
+{
+ sensor_speed = 0,
+ sensor_autoclean,
+ sensor_gas,
+ sensor_rht,
+ sensor_laser,
+ sensor_fan
+};
+
+
+
+//////////////////////////////////////////////////////////////////////////////////////////////////
+// ESPeasy standard PluginTaskData structure for this plugin
+//struct P167_data_struct : public PluginTaskData_base
+class P167_data_struct
+{
+
+public:
+ P167_data_struct();
+ //virtual ~P167_data_struct();
+ ~P167_data_struct();
+
+ void checkPin_interrupt(void);
+
+ /////////////////////////////////////////////////////////
+ // This method runs the FSM step by step on each call
+ // Returns true when a stable state is reached
+ bool update();
+ bool monitorSCL();
+
+ /////////////////////////////////////////////////////////
+ // (re)configure the device properties
+ // This will result in resetting and reloading the device
+ bool setupDevice(uint8_t i2caddr);
+ bool setupModel(uint8_t model);
+ bool setupMonPin(uint8_t monpin);
+ void enableInterrupt_monpin(void);
+ void disableInterrupt_monpin(void);
+
+ /////////////////////////////////////////////////////////
+ // check sensor is reachable over I2C
+ bool isConnected() const;
+
+ /////////////////////////////////////////////////////////
+ bool newValues() const;
+
+ /////////////////////////////////////////////////////////
+ bool inError() const;
+
+ /////////////////////////////////////////////////////////
+ // Reset the FSM to initial state
+ bool reset();
+
+ /////////////////////////////////////////////////////////
+ // Trigger a measurement cycle
+ // Only perform the measurements with big interval to prevent the sensor from warming up.
+ bool startMeasurements();
+
+ bool getStatusInfo(param_statusinfo param);
+ /////////////////////////////////////////////////////////
+ // Electronic Identification Code
+ // Sensirion_Humidity_SHT2x_Electronic_Identification_Code_V1.1.pdf
+ // Electronic ID bytes
+ bool getEID(String &eid_productname, String &eid_serialnumber, uint8_t &firmware) const;
+
+ /////////////////////////////////////////////////////////
+ // Temperature, humidity, DewPoint, PMxpy retrieval
+ // Note: values are fetched from memory and reflect latest succesful read cycle
+ float getRequestedValue(uint8_t request) const;
+
+
+ uint16_t getErrCode(bool _clear = false); //return last errorcode (optional clear this value, default false)
+ uint16_t getErrCount(bool _clear = false); //return total errors count (optional clear this value, default false)
+ uint16_t getSuccCount(bool _clear = false); //return total success count (optional clear this value, default false)
+ void clearErrCode(); //clear last errorcode
+ void clearErrCount(); //clear total errors count
+ void clearSuccCount(); //clear total success count
+
+//protected:
+private:
+
+ union devicestatus
+ {
+ uint32_t val;
+ struct
+ {
+ uint16_t dummy1:10;
+ bool speed;
+ bool dummy2;
+ bool autoclean;
+ uint16_t dummy3:11;
+ bool gas;
+ bool rht;
+ bool laser;
+ bool fan;
+ uint16_t dummy4:4;
+ };
+ };
+
+ devicestatus _devicestatus;
+ P167_state _state;
+
+ uint8_t crc8(const uint8_t *data, uint8_t len);
+ bool writeCmd(uint16_t cmd);
+ bool writeCmd(uint16_t cmd, uint8_t value);
+ bool writeCmd(uint16_t cmd, uint8_t length, uint8_t *buffer);
+ bool readBytes(uint8_t n, uint8_t *val, uint8_t maxDuration);
+
+ bool readMeasValue();
+ bool readMeasRawValue();
+ bool readMeasRawMYSValue();
+ bool readDataRdyFlag();
+ bool readDeviceStatus();
+ bool calculateValue();
+
+ bool getProductName();
+ bool getSerialNumber();
+ bool getFirmwareVersion();
+
+
+ float _Humidity; // Humidity as fetched from the device [bits]
+ float _HumidityX; // Humidity as calculated
+ float _Temperature; // Temperature as fetched from the device [bits]
+ float _TemperatureX; // Temperature as calculated
+ float _DewPoint; // DewPoint as calculated
+ float _rawHumidity; // Humidity as fetched from the device without compensation[bits]
+ float _rawTemperature; // Temperature as fetched from the device without compensation[bits]
+ float _mysHumidity; // Humidity as fetched from the device without compensation[bits]
+ float _mysTemperature; // Temperature as fetched from the device without compensation[bits]
+ float _tVOC; // tVOC as fetched from the device[bits]
+ float _NOx; // NOx as fetched from the device[bits]
+ float _rawtVOC; // tVOC as fetched from the device without compensation[bits]
+ float _rawNOx; // NOx as fetched from the device without compensation[bits]
+ float _mysOffset; // Temperature Offset fetched from the device[bits]
+ float _PM1p0; // PM1.0 as fetched from the device[bits]
+ float _PM2p5; // PM2.5 as fetched from the device[bits]
+ float _PM4p0; // PM4.0 as fetched from the device[bits]
+ float _PM10p0; // PM10.0 as fetched from the device[bits]
+ uint8_t _model; // Selected sensor model
+ uint8_t _i2caddr; // Programmed I2C address
+ uint8_t _monpin; // Pin to monitor I2C SCL to find when VindStyrka finish i2c communication
+ unsigned long _last_action_started; // Timestamp for last action that takes processing time
+ uint16_t _errCount; // Number of errors since last successful access
+ String _eid_productname; // Electronic Device ID - Product Name, read at initialization
+ String _eid_serialnumber; // Electronic Device ID - Serial Number, read at initialization
+ uint8_t _firmware; // Firmware version numer, read at initialization
+ uint8_t _userreg; // TODO debugging only
+ uint16_t _readingerrcode = VIND_ERR_NO_ERROR; // 4 = timeout; 3 = not enough bytes; 2 = number of bytes OK but bytes b0,b1 or b2 wrong, 1 = crc error
+ uint16_t _readingerrcount = 0; // total errors couter
+ uint32_t _readingsuccesscount = 0; // total success couter
+ bool _errmeas;
+ bool _errmeasraw;
+ bool _errmeasrawmys;
+ bool _errdevicestatus;
+ uint8_t stepMonitoring; // step for Monitorin SCL pin algorithm
+ bool startMonitoringFlag; // flag to START/STOP Monitoring algorithm
+ bool statusMonitoring; // flag for status return from Monitoring algorithm
+ unsigned long lastSCLLowTransitionMonitoringTime; // last time when SCL i2c pin rising
+
+ volatile uint32_t monpinValue = 0;
+ volatile uint32_t monpinValuelast = 0;
+ volatile uint8_t monpinChanged = 0;
+ volatile uint64_t monpinLastTransitionTime = 0;
+
+};
+#endif // USES_P167
\ No newline at end of file