diff --git a/src/_P052_SenseAir.ino b/src/_P052_SenseAir.ino index e20b6fd301..b58a1a0e88 100644 --- a/src/_P052_SenseAir.ino +++ b/src/_P052_SenseAir.ino @@ -109,59 +109,6 @@ boolean Plugin_052(uint8_t function, struct EventStruct *event, String& string) break; } - - case PLUGIN_WRITE: { - String cmd = parseString(string, 1); - String param1 = parseString(string, 2); - - if (cmd.equalsIgnoreCase(F("senseair_setrelay"))) { - int par1; - - if (validIntFromString(param1, par1)) { - if ((par1 == 0) || (par1 == 1) || (par1 == -1)) { - short relaystatus = 0; // 0x3FFF represents 100% output. - - // Refer to sensor model’s specification for voltage at 100% output. - switch (par1) { - case 0: - relaystatus = 0; - break; - case 1: - relaystatus = 0x3FFF; - break; - default: - relaystatus = 0x7FFF; - break; - } - P052_data_struct *P052_data = - static_cast(getPluginTaskData(event->TaskIndex)); - - if ((nullptr != P052_data) && P052_data->isInitialized()) { - P052_data->modbus.writeSingleRegister(0x18, relaystatus); - addLog(LOG_LEVEL_INFO, concat(F("Senseair command: relay="), param1)); - } - } - } - success = true; - } - - /* - // ABC functionality disabled for now, due to a bug in the firmware. - // See https://github.com/letscontrolit/ESPEasy/issues/759 - if (cmd.equalsIgnoreCase(F("senseair_setABCperiod"))) - { - if (param1.toInt() >= 0) { - Plugin_052_setABCperiod(param1.toInt()); - addLog(LOG_LEVEL_INFO, String(F("Senseair command: ABCperiod=")) + - param1); - } - success = true; - } - */ - - break; - } - case PLUGIN_WEBFORM_LOAD_OUTPUT_SELECTOR: { const __FlashStringHelper *options[P052_NR_OUTPUT_OPTIONS]; @@ -184,10 +131,22 @@ boolean Plugin_052(uint8_t function, struct EventStruct *event, String& string) if ((nullptr != P052_data) && P052_data->isInitialized()) { addFormSubHeader(F("Device Information")); { + int value = 0; + if (P052_data->modbus.detected_device_description.length() > 0) { addRowLabel(F("Detected Device")); addHtml(P052_data->modbus.detected_device_description); } + + if (P052_data->readInputRegister(P052_IR29_FW_REV, value)) { + addRowLabel(F("FW rev.")); + addHtmlInt(value); + } + + addRowLabel(F("Sensor ID")); + addHtml(formatToHex_decimal(P052_data->getSensorID())); + + addRowLabel(F("Checksum (pass/fail/nodata)")); { uint32_t reads_pass, reads_crc_failed, reads_nodata; @@ -201,58 +160,92 @@ boolean Plugin_052(uint8_t function, struct EventStruct *event, String& string) addHtml(chksumStats); } - uint8_t errorcode = 0; - int value = P052_data->modbus.readInputRegister(0x06, errorcode); + bool hasFactorySettings = false; + bool enabledABC = false; + + if (P052_data->readHoldingRegister(P052_HR19_METER_CONTROL, value)) { + hasFactorySettings = value == 255; + enabledABC = bitRead(value, 1) == 0; + addRowLabel(F("Using Factory Settings")); + addEnabled(hasFactorySettings); + + if (!hasFactorySettings) { + // addRowLabel(F("HR19")); + // addHtmlInt(value); + addRowLabel(F("nRDY")); + addEnabled(bitRead(value, 0) == 0); + addRowLabel(F("ABC")); + addEnabled(enabledABC); + addRowLabel(F("Static IIR filter")); + addEnabled(bitRead(value, 2) == 0); + addRowLabel(F("Dynamic IIR filter")); + addEnabled(bitRead(value, 3) == 0); + addRowLabel(F("Pressure Compensation")); + addEnabled(bitRead(value, 4) == 0); + } + } + - if (errorcode == 0) { + if (P052_data->readInputRegister(P052_IR7_MEASUREMENT_COUNT, value)) { addRowLabel(F("Measurement Count")); addHtmlInt(value); } - value = P052_data->modbus.readInputRegister(0x07, errorcode); - - if (errorcode == 0) { + if (P052_data->readInputRegister(P052_IR8_MEASUREMENT_CYCLE_TIME, value)) { addRowLabel(F("Measurement Cycle time")); addHtmlInt(value * 2); + addUnit('s'); } - value = P052_data->modbus.readInputRegister(0x08, errorcode); - - if (errorcode == 0) { + if (P052_data->readInputRegister(P052_IR9_MEASURED_UNFILTERED_CO2, value)) { addRowLabel(F("Unfiltered CO2")); addHtmlInt(value); } - } - { - uint8_t errorcode = 0; + if (P052_data->readHoldingRegister(P052_HR11_MEASUREMENT_MODE, value)) { + addRowLabel(F("Measurement Mode")); + addHtml(value == 0 ? F("Continuous") : F("Single Measurement")); + } - // int meas_mode = P052_data->modbus.readHoldingRegister(0x0A, errorcode); - // bool has_meas_mode = errorcode == 0; - int period = P052_data->modbus.readHoldingRegister(0x0B, errorcode); - bool has_period = errorcode == 0; - int samp_meas = P052_data->modbus.readHoldingRegister(0x0C, errorcode); - bool has_samp_meas = errorcode == 0; - - if (/* has_meas_mode || */ has_period || has_samp_meas) { - // Disable selector for now, since single measurement not yet supported. - - /* - if (has_meas_mode) { - const __FlashStringHelper * options[2] = { F("Continuous"), F("Single Measurement") }; - addFormSelector(F("Measurement Mode"), F("mode"), 2, options, nullptr, meas_mode); - } - */ - if (has_period) { - addFormNumericBox(F("Measurement Period"), F("period"), period, 2, 65534); - addUnit('s'); + if (enabledABC) { + if (P052_data->readHoldingRegister(P052_HR14_ABC_PERIOD, value)) { + addRowLabel(F("ABC Period")); + addHtmlInt(value); + addUnit('h'); } - if (has_samp_meas) { - addFormNumericBox(F("Samples per measurement"), F("samp_meas"), samp_meas, 1, 1024); + if (P052_data->readHoldingRegister(P052_HR5_ABC_TIME, value)) { + addRowLabel(F("Time Since ABC")); + addHtmlInt(value); + addUnit('h'); } } } + + { + int value = 0; + + /* + if (P052_data->readHoldingRegister(P052_HR11_MEASUREMENT_MODE, value)) { + // Disable selector for now, since single measurement not yet supported. + + const __FlashStringHelper *options[2] = { F("Continuous"), F("Single Measurement") }; + addFormSelector(F("Measurement Mode"), F("mode"), 2, options, nullptr, value); + } + */ + + if (P052_data->readHoldingRegister(P052_HR12_MEASUREMENT_PERIOD, value)) { + addFormNumericBox(F("Measurement Period"), F("period"), value, 2, 65534); + addUnit('s'); + } + + if (P052_data->readHoldingRegister(P052_HR13_NR_OF_SAMPLES, value)) { + addFormNumericBox(F("Samples per measurement"), F("samp_meas"), value, 1, 1024); + } + } + { + // P052_ABC_PERIOD + } } /* @@ -396,30 +389,35 @@ boolean Plugin_052(uint8_t function, struct EventStruct *event, String& string) switch (PCONFIG(varnr)) { case 1: { - value = P052_data->modbus.readInputRegister(P052_IR_SPACE_CO2, errorcode); + value = P052_data->modbus.readInputRegister(P052_IR4_MEASURED_FILTERED_CO2, errorcode); logPrefix = F("co2 = "); break; } case 2: { - int temperatureX100 = P052_data->modbus.readInputRegister(P052_IR_TEMPERATURE, errorcode); + int temperatureX100 = P052_data->modbus.readInputRegister(P052_IR5_TEMPERATURE, errorcode); - if (errorcode != 0) { + if (errorcode == 2) { + // Exception code: Illegal Data Address // SenseAir S8, not for other modules. temperatureX100 = P052_data->modbus.read_RAM_EEPROM( P052_CMD_READ_RAM, P052_RAM_ADDR_DET_TEMPERATURE, 2, errorcode); } + if (temperatureX100 >= 32768) { + // 2-complement value. + temperatureX100 -= 65536; + } value = static_cast(temperatureX100) / 100.0f; logPrefix = F("temperature = "); break; } case 3: { - int rhX100 = P052_data->modbus.readInputRegister(P052_IR_SPACE_HUMIDITY, errorcode); + int rhX100 = P052_data->modbus.readInputRegister(P052_IR6_SPACE_HUMIDITY, errorcode); value = static_cast(rhX100) / 100.0f; logPrefix = F("humidity = "); break; } case 4: { - int status = P052_data->modbus.readInputRegister(0x1C, errorcode); + int status = P052_data->modbus.readInputRegister(P052_IR29_FW_REV, errorcode); if (errorcode == 0) { int relayStatus = (status >> 8) & 0x1; @@ -430,7 +428,7 @@ boolean Plugin_052(uint8_t function, struct EventStruct *event, String& string) break; } case 5: { - int temperatureAdjustment = P052_data->modbus.readInputRegister(0x0A, errorcode); + int temperatureAdjustment = P052_data->modbus.readInputRegister(P052_IR11_MEASURED_CONCENTRATION_UNFILTERED, errorcode); value = static_cast(temperatureAdjustment); logPrefix = F("temperature adjustment = "); break; @@ -441,6 +439,7 @@ boolean Plugin_052(uint8_t function, struct EventStruct *event, String& string) if (errorcode == 0) { value = errorWord; + for (size_t i = 0; i < 9; i++) { if (bitRead(errorWord, i)) { log += F("error code = "); @@ -462,8 +461,8 @@ boolean Plugin_052(uint8_t function, struct EventStruct *event, String& string) } } - if (P052_data->modbus.getLastError() == 0) { - success = true; + if (errorcode == 0 && P052_data->modbus.getLastError() == 0) { + success = true; UserVar[event->BaseVarIndex + varnr] = value; log += logPrefix; log += value; @@ -474,6 +473,107 @@ boolean Plugin_052(uint8_t function, struct EventStruct *event, String& string) } break; } + + case PLUGIN_WRITE: + { + P052_data_struct *P052_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + if ((nullptr != P052_data) && P052_data->isInitialized()) { + const String command = parseString(string, 1); + + if (equals(command, F("senseair"))) { + const String subcommand = parseString(string, 2); + + if (equals(subcommand, F("writehr"))) { + // FIXME TD-er: Must test, never tested on real hardware yet.... + + uint32_t addr = 0; + uint32_t cmnd = 0; + + if (validUIntFromString(parseString(string, 3), addr) && + validUIntFromString(parseString(string, 4), cmnd)) { + uint8_t errorcode = 0; + P052_data->modbus.writeSingleRegister(addr, cmnd, errorcode); + success = 0 == errorcode; + } + } else if (equals(subcommand, F("readhr"))) { + uint32_t addr = 0; + + if (validUIntFromString(parseString(string, 3), addr)) { + uint8_t errorcode = 0; + const int value = P052_data->modbus.readHoldingRegister(addr, errorcode); + + if (0 == errorcode) { + success = true; + + if (Settings.UseRules) { + String eventvalues; + eventvalues += (addr + 1); // HR1 = addr 0x00 + eventvalues += ','; + eventvalues += value; + eventQueue.add(event->TaskIndex, F("readhr"), eventvalues); + } + } + } + } else if (equals(subcommand, F("enableabc"))) { + uint32_t hours = 0; + + if (validUIntFromString(parseString(string, 3), hours)) { + // FIXME TD-er: Implement + + + // Read HR19 + + // Clear bit 1 in register and write back HR19 + + // Read HR14 and verify desired ABC period + + // If HR14 (ABC period) is not the desired period, + // write desired ABC period to HR14 + } + } else if (equals(subcommand, F("disableabc"))) { + // FIXME TD-er: Implement + + // Read HR19 + + // Set bit 1 in register and write back HR19 + } else if (equals(subcommand, F("setabcperiod"))) { + // FIXME TD-er: Must test, never tested on real hardware yet.... + + int period = 0; + + if (validIntFromString(parseString(string, 3), period)) { + if (period >= 0) { + P052_data->setABCperiod(period); + addLog(LOG_LEVEL_INFO, String(F("Senseair command: ABCperiod=")) + + period); + success = true; + } + } + } else if (equals(subcommand, F("setrelay"))) { + int state = 0; + + if (validIntFromString(parseString(string, 3), state)) { + short relaystatus = 0; // 0x3FFF represents 100% output. + success = true; + + // Refer to sensor model’s specification for voltage at 100% output. + if (state == 0) { relaystatus = 0; } + else if (state == 1) { relaystatus = 0x3FFF; } + else if (state == -1) { relaystatus = 0x7FFF; } + else { success = false; } + + if (success) { + P052_data->modbus.writeSingleRegister(0x18, relaystatus); + addLog(LOG_LEVEL_INFO, concat(F("Senseair command: relay="), state)); + } + } + } + } + } + break; + } } return success; } diff --git a/src/src/PluginStructs/P052_data_struct.cpp b/src/src/PluginStructs/P052_data_struct.cpp index a362c2eaab..9de5298972 100644 --- a/src/src/PluginStructs/P052_data_struct.cpp +++ b/src/src/PluginStructs/P052_data_struct.cpp @@ -37,4 +37,58 @@ const __FlashStringHelper * P052_data_struct::Plugin_052_valuename(uint8_t value return F(""); } -#endif // ifdef USES_P052 \ No newline at end of file +void P052_data_struct::setABCperiod(int hours) +{ + // Enable: write 1 ... 65534 to HR14 and bit1 set to 0 at HR19 + // Disable: write 0 or 65535 to HR14 and bit1 set to 1 at HR19 + + + // Read HR19 + uint8_t errorcode = 0; + int value = modbus.readHoldingRegister(P052_HR19_METER_CONTROL, errorcode); + + // Clear bit 1 in register and write back HR19 + if (bitRead(value, 1)) { + bitClear(value, 1); + modbus.writeSingleRegister(P052_HR19_METER_CONTROL, value, errorcode); + } + + // Read HR14 and verify desired ABC period + value = modbus.readHoldingRegister(P052_HR14_ABC_PERIOD, errorcode); + + // If HR14 (ABC period) is not the desired period, + // write desired ABC period to HR14 + if (value != hours) { + modbus.writeSingleRegister(P052_HR14_ABC_PERIOD, hours, errorcode); + } +} + +uint32_t P052_data_struct::getSensorID() +{ + uint8_t errorcode = 0; + const uint32_t sensorId = (modbus.readInputRegister(P052_IR30_SENSOR_ID_HIGH, errorcode) << 16) | + modbus.readInputRegister(P052_IR31_SENSOR_ID_LOW, errorcode); + + if (errorcode == 0) { + return sensorId; + } + return 0; +} + +bool P052_data_struct::readInputRegister(short addr, int& value) +{ + uint8_t errorcode = 0; + + value = modbus.readInputRegister(addr, errorcode); + return errorcode == 0; +} + +bool P052_data_struct::readHoldingRegister(short addr, int& value) +{ + uint8_t errorcode = 0; + + value = modbus.readHoldingRegister(addr, errorcode); + return errorcode == 0; +} + +#endif // ifdef USES_P052 diff --git a/src/src/PluginStructs/P052_data_struct.h b/src/src/PluginStructs/P052_data_struct.h index 619ce482e0..5be47b82c7 100644 --- a/src/src/PluginStructs/P052_data_struct.h +++ b/src/src/PluginStructs/P052_data_struct.h @@ -10,6 +10,7 @@ # define P052_QUERY1_CONFIG_POS 0 # define P052_SENSOR_TYPE_INDEX (P052_QUERY1_CONFIG_POS + (VARS_PER_TASK + 1)) +# define P052_ABC_PERIOD (P052_SENSOR_TYPE_INDEX + 1) # define P052_NR_OUTPUT_VALUES getValueCountFromSensorType(static_cast(PCONFIG(P052_SENSOR_TYPE_INDEX))) # define P052_NR_OUTPUT_OPTIONS 8 @@ -55,22 +56,33 @@ # define P052_IR_ERRORSTATUS 0 # define P052_IR_ALARMSTATUS 1 # define P052_IR_OUTPUTSTATUS 2 -# define P052_IR_SPACE_CO2 3 // also called CO2 value filtered -# define P052_IR_TEMPERATURE 4 // Chip temperature in 1/100th degree C -# define P052_IR_SPACE_HUMIDITY 5 -# define P052_IR_MEASUREMENT_COUNT 6 // Range 0 .. 255, to see if a measurement has been done. -# define P052_IR_MEASUREMENT_CYCLE_TIME 7 // Time in current cycle (in 2 seconds steps) -# define P052_IR_CO2_UNFILTERED 8 - - -# define P052_HR_ACK_REG 0 -# define P052_HR_SPACE_CO2 3 -# define P052_HR_ABC_PERIOD 31 +# define P052_IR4_MEASURED_FILTERED_CO2 0x03 +# define P052_IR5_TEMPERATURE 0x04 // Chip temperature in 1/100th degree C +# define P052_IR6_SPACE_HUMIDITY 0x05 +# define P052_IR7_MEASUREMENT_COUNT 0x06 // Range 0 .. 255, to see if a measurement has been done. +# define P052_IR8_MEASUREMENT_CYCLE_TIME 0x07 // Time in current cycle (in 2 seconds steps) +# define P052_IR9_MEASURED_UNFILTERED_CO2 0x08 +# define P052_IR11_MEASURED_CONCENTRATION_UNFILTERED 0x0A + +# define P052_IR24_FW_TYPE 0x17 +# define P052_IR29_FW_REV 0x1C +# define P052_IR30_SENSOR_ID_HIGH 0x1D +# define P052_IR31_SENSOR_ID_LOW 0x1E + +// HR (Holding Register) +# define P052_HR1_CALIBRATION_STATUS 0 +# define P052_HR2_CALIBRATION_COMMAND 0x01 +# define P052_HR5_ABC_TIME 0x04 +# define P052_HR11_MEASUREMENT_MODE 0x0A +# define P052_HR12_MEASUREMENT_PERIOD 0x0B // Measurement period in seconds +# define P052_HR13_NR_OF_SAMPLES 0x0C +# define P052_HR14_ABC_PERIOD 0x0D +# define P052_HR19_METER_CONTROL 0x12 // #define P052_MODBUS_SLAVE_ADDRESS 0x68 # define P052_MODBUS_SLAVE_ADDRESS 0xFE // Modbus "any address" -# define P052_MODBUS_TIMEOUT 180 // 100 msec communication timeout. +# define P052_MODBUS_TIMEOUT 180 // 180 msec communication timeout. # include @@ -94,6 +106,18 @@ struct P052_data_struct : public PluginTaskData_base { static const __FlashStringHelper* Plugin_052_valuename(uint8_t value_nr, bool displayString); + void setABCperiod(int hours); + + uint32_t getSensorID(); + + // Return true, when read was successful + bool readInputRegister(short addr, + int & value); + + // Return true, when read was successful + bool readHoldingRegister(short addr, + int & value); + ModbusRTU_struct modbus; };