diff --git a/WThermostat/src/WThermostat.cpp b/WThermostat/src/WThermostat.cpp index 7f765db..2536619 100644 --- a/WThermostat/src/WThermostat.cpp +++ b/WThermostat/src/WThermostat.cpp @@ -5,6 +5,7 @@ #include "WThermostat_BHT_002_GBLW.h" #include "WThermostat_BAC_002_ALW.h" #include "WThermostat_ET81W.h" +#include "WThermostat_NX_4608.h" #include "WThermostat_HY08WE.h" #include "WThermostat_ME81H.h" #include "WThermostat_MK70GBH.h" @@ -65,6 +66,9 @@ void setup() { case MODEL_DLX_LH01 : device = new WThermostat_DLX_LH01(network, thermostatModel, wClock); break; + case MODEL_NX_4608 : + device = new WThermostat_NX_4608(network, thermostatModel, wClock); + break; default : network->error(F("Can't start device. Wrong thermostatModel (%d)"), thermostatModel->getByte()); } diff --git a/WThermostat/src/WThermostat.h b/WThermostat/src/WThermostat.h index 3454aed..43ff745 100644 --- a/WThermostat/src/WThermostat.h +++ b/WThermostat/src/WThermostat.h @@ -16,6 +16,7 @@ #define MODEL_ME102H 6 #define MODEL_CALYPSOW 7 #define MODEL_DLX_LH01 8 +#define MODEL_NX_4608 9 #define PIN_STATE_HEATING_RELAY 5 #define NOT_SUPPORTED 0x00 @@ -139,6 +140,7 @@ public : page->printf(HTTP_COMBOBOX_ITEM, "5", (this->thermostatModel->getByte() == 5 ? HTTP_SELECTED : ""), "Minco Heat MK70GB-H"); page->printf(HTTP_COMBOBOX_ITEM, "7", (this->thermostatModel->getByte() == 7 ? HTTP_SELECTED : ""), "VH Control Calypso-W"); page->printf(HTTP_COMBOBOX_ITEM, "8", (this->thermostatModel->getByte() == 8 ? HTTP_SELECTED : ""), "DLX-LH01"); + page->printf(HTTP_COMBOBOX_ITEM, "9", (this->thermostatModel->getByte() == 9 ? HTTP_SELECTED : ""), "Revolt NX-4608"); page->print(FPSTR(HTTP_COMBOBOX_END)); //Checkbox page->printf(HTTP_CHECKBOX_OPTION, "sb", "sb", (this->switchBackToAuto->getBoolean() ? HTTP_CHECKED : ""), "", "Auto mode from manual mode at next schedule period change
(not at model ET-81W and ME81AH)"); @@ -305,8 +307,8 @@ public : bool heating = false; if ((this->supportingHeatingRelay->getBoolean()) && (state != nullptr)) { heating = digitalRead(PIN_STATE_HEATING_RELAY); + this->state->setString(heating ? STATE_HEATING : STATE_OFF); } - this->state->setString(heating ? STATE_HEATING : STATE_OFF); } WTuyaDevice::loop(now); updateCurrentSchedulePeriod(); diff --git a/WThermostat/src/WThermostat_NX_4608.h b/WThermostat/src/WThermostat_NX_4608.h new file mode 100644 index 0000000..c9976ae --- /dev/null +++ b/WThermostat/src/WThermostat_NX_4608.h @@ -0,0 +1,251 @@ +#ifndef THERMOSTAT_NX_4608_H +#define THERMOSTAT_NX_4608_H + +#include +#include +#include "WThermostat.h" +#include "WThermostat_ME102H.h" + +class WThermostat_NX_4608 : public WThermostat { +public : + WThermostat_NX_4608(WNetwork* network, WProperty* thermostatModel, WClock* wClock) + : WThermostat(network, thermostatModel, wClock) { + network->debug(F("WThermostat_NX_4608 created")); + } + + virtual void configureCommandBytes() { + this->byteDeviceOn = 0x01; + this->byteTemperatureActual = 0x03; + this->byteTemperatureTarget = 0x02; + this->byteTemperatureFloor = 0x67; + this->temperatureFactor = 10.0f; + this->byteSchedulesMode = 0x04; + this->byteLocked = 0x06; + this->byteSchedules = NOT_SUPPORTED; + this->byteSchedulingPosHour = 1; + this->byteSchedulingPosMinute = 0; + this->byteSchedulingDays = 18; + //custom + this->byteState = 0x66; + this->byteSensorSelection = 0x74; + } + + virtual void initializeProperties() { + WThermostat::initializeProperties(); + //2021-01-14 - schedulesMode + this->schedulesMode->clearEnums(); + this->schedulesMode->addEnumString(SCHEDULES_MODE_OFF); + this->schedulesMode->addEnumString(SCHEDULES_MODE_AUTO); + this->schedulesMode->addEnumString(SCHEDULES_MODE_HOLIDAY); + this->schedulesMode->addEnumString(SCHEDULES_MODE_HOLD); + //sensorSelection + this->sensorSelection = new WProperty("sensorSelection", "Sensor Selection", STRING, TYPE_THERMOSTAT_MODE_PROPERTY); + this->sensorSelection->addEnumString(SENSOR_SELECTION_INTERNAL); + this->sensorSelection->addEnumString(SENSOR_SELECTION_FLOOR); + this->sensorSelection->addEnumString(SENSOR_SELECTION_BOTH); + this->sensorSelection->setVisibility(MQTT); + this->sensorSelection->setOnChange(std::bind(&WThermostat_NX_4608::sensorSelectionToMcu, this, std::placeholders::_1)); + this->addProperty(this->sensorSelection); + //Heating Relay and State property + this->state = new WProperty("state", "State", STRING, TYPE_HEATING_COOLING_PROPERTY); + this->state->setReadOnly(true); + this->state->addEnumString(STATE_OFF); + this->state->addEnumString(STATE_HEATING); + this->addProperty(state); + } + +protected : + + virtual bool processStatusCommand(byte cByte, byte commandLength) { + //Status report from MCU + bool changed = false; + bool knownCommand = WThermostat::processStatusCommand(cByte, commandLength); + + if (!knownCommand) { + const char* newS; + if (cByte == this->byteSensorSelection) { + if (commandLength == 0x05) { + //sensor selection - + //internal: 55 aa 03 07 00 05 74 04 00 01 00 + //floor: 55 aa 03 07 00 05 74 04 00 01 01 + //both: 55 aa 03 07 00 05 74 04 00 01 02 + newS = this->sensorSelection->getEnumString(receivedCommand[10]); + if (newS != nullptr) { + changed = ((changed) || (this->sensorSelection->setString(newS))); + knownCommand = true; + } + } + } + else if (cByte == this->byteState) { + if (commandLength == 0x05){ + //heating state + // on: 55 aa 03 07 00 05 01 01 00 01 01 + // off: 55 aa 03 07 00 05 01 01 00 01 00 + newS = this->state->getEnumString(receivedCommand[10]); + if (newS != nullptr) { + changed = ((changed) || (this->state->setString(newS))); + } + knownCommand = true; + } + } else{ + //consume some unsupported commands + switch (cByte) { + case 0x0c : + // unknown bitmap + // MCU: 55 aa 03 07 00 05 0c 05 00 01 00 + // MCU: 55 aa 03 07 00 05 0c 05 00 01 08 + knownCommand = true; + break; + case 0x65 : + // unknown boolean + // MCU: 55 aa 03 07 00 05 65 01 00 01 00 + knownCommand = true; + break; + case 0x68 : + // number of holiday days + // MCU: 5d / 55 aa 03 07 00 08 68 02 00 04 00 00 00 05 + // MCU: 10d / 55 aa 03 07 00 08 68 02 00 04 00 00 00 0a + knownCommand = true; + break; + case 0x69 : + // unknown integer + // MCU: 55 aa 03 07 00 08 69 02 00 04 00 00 00 0f + knownCommand = true; + break; + case 0x6a : + // disable high temp protection for external sensor (AA Code 2) + // MCU: off / 55 aa 03 07 00 05 6a 01 00 01 00 + // MCU: on / 55 aa 03 07 00 05 6a 01 00 01 01 + knownCommand = true; + break; + case 0x6b : + // disable low temp protection for external sensor (A9 Code 2) + // MCU: off / 55 aa 03 07 00 05 6b 01 00 01 00 + // MCU: on / 55 aa 03 07 00 05 6b 01 00 01 01 + knownCommand = true; + break; + case 0x6c : + // unknown boolean + // MCU: 55 aa 03 07 00 05 6c 01 00 01 00 + knownCommand = true; + break; + case 0x6d : + // Temperature correction in millidegree (A1) + // MCU: 0C / 55 aa 03 07 00 08 6d 02 00 04 00 00 00 00 + // MCU: -2C / 55 aa 03 07 00 08 6d 02 00 04 ff ff ff ec + knownCommand = true; + break; + case 0x6e : + // hysteresis in millidegree (A2) + // MCU: 0.5C / 55 aa 03 07 00 08 6e 02 00 04 00 00 00 05 + // MCU: 1C / 55 aa 03 07 00 08 6e 02 00 04 00 00 00 0a + // MCU: 1.5C / 55 aa 03 07 00 08 6e 02 00 04 00 00 00 0f + // MCU: 2C / 55 aa 03 07 00 08 6e 02 00 04 00 00 00 14 + // MCU: 2.5C / 55 aa 03 07 00 08 6e 02 00 04 00 00 00 19 + knownCommand = true; + break; + case 0x6f : + // hysteresis in degree for external sensor (AB) + // MCU: 1C / 55 aa 03 07 00 08 6f 02 00 04 00 00 00 01 + // MCU: 2C / 55 aa 03 07 00 08 6f 02 00 04 00 00 00 02 + // ... + // MCU: 9C / 55 aa 03 07 00 08 6f 02 00 04 00 00 00 09 + knownCommand = true; + break; + case 0x70 : + // max temp external sensor (AA Code 1) + // MCU: 35C / 55 aa 03 07 00 08 70 02 00 04 00 00 00 23 + // ... + // MCU: 70C / 55 aa 03 07 00 08 70 02 00 04 00 00 00 46 + case 0x71 : + // min temp external sensor (A9 Code 1) + // MCU: 1C / 55 aa 03 07 00 08 71 02 00 04 00 00 00 01 + // MCU: 5C / 55 aa 03 07 00 08 71 02 00 04 00 00 00 05 + // MCU: 10C / 55 aa 03 07 00 08 71 02 00 04 00 00 00 0a + knownCommand = true; + break; + case 0x72 : + // max temp (A8) + // MCU: 20C / 55 aa 03 07 00 08 72 02 00 04 00 00 00 14 + // MCU: 21C / 55 aa 03 07 00 08 72 02 00 04 00 00 00 15 + // ... + // MCU: 35C / 55 aa 03 07 00 08 72 02 00 04 00 00 00 23 + // ... + // MCU: 70C / 55 aa 03 07 00 08 72 02 00 04 00 00 00 46 + knownCommand = true; + break; + case 0x73 : + // min temp (A7) + // MCU: 1C / 55 aa 03 07 00 08 73 02 00 04 00 00 00 01 + // MCU: 2C / 55 aa 03 07 00 08 73 02 00 04 00 00 00 02 + // MCU: 3C / 55 aa 03 07 00 08 73 02 00 04 00 00 00 03 + // ... + // MCU: 9C / 55 aa 03 07 00 08 73 02 00 04 00 00 00 09 + // MCU: 10C / 55 aa 03 07 00 08 73 02 00 04 00 00 00 0a + knownCommand = true; + break; + case 0x75 : + // poweron state (A4) + // saved: 55 aa 03 07 00 05 75 04 00 01 00 + // off: 55 aa 03 07 00 05 75 04 00 01 01 + // on: 55 aa 03 07 00 05 75 04 00 01 02 + knownCommand = true; + break; + case 0x76 : + // weekend mode (A6) + // MCU: 5+2 / 55 aa 03 07 00 05 76 04 00 01 00 + // MCU: 6+1 / 55 aa 03 07 00 05 76 04 00 01 01 + // MCU: 7 / 55 aa 03 07 00 05 76 04 00 01 02 + knownCommand = true; + break; + case 0x77 : + // unknown raw + // MCU: 55 aa 03 07 00 0d 77 00 00 09 06 00 14 08 00 0f 0b 1e 0f + knownCommand = true; + break; + case 0x78 : + // unknown raw + // MCU: 55 aa 03 07 00 0d 78 00 00 09 0d 1e 0f 11 00 0f 96 00 0f + knownCommand = true; + break; + case 0x79 : + // unknown raw + // MCU: 55 aa 03 07 00 0d 79 00 00 09 06 00 14 08 00 0f 0b 1e 0f + knownCommand = true; + break; + case 0x7A : + // unknown raw + // MCU: 55 aa 03 07 00 0d 7a 00 00 09 0d 1e 0f 11 00 0f 16 00 0f + knownCommand = true; + break; + } + } + } + if (changed) { + notifyState(); + } + return knownCommand; + } + + void sensorSelectionToMcu(WProperty* property) { + if (!isReceivingDataFromMcu()) { + byte sm = property->getEnumIndex(); + if (sm != 0xFF) { + //send to device + //internal: 55 aa 03 06 00 05 74 05 00 01 00 + //floor: 55 aa 03 06 00 05 74 05 00 01 01 + //both: 55 aa 03 06 00 05 74 05 00 01 02 + unsigned char cm[] = { 0x55, 0xAA, 0x03, 0x06, 0x00, 0x05, + this->byteSensorSelection, 0x05, 0x00, 0x01, sm}; + commandCharsToSerial(11, cm); + } + } + } + +private : + WProperty* sensorSelection; + byte byteSensorSelection; + byte byteState; +}; + +#endif diff --git a/docs/NX-4608/NX4608_11_173788.pdf b/docs/NX-4608/NX4608_11_173788.pdf new file mode 100644 index 0000000..41b9097 Binary files /dev/null and b/docs/NX-4608/NX4608_11_173788.pdf differ diff --git a/docs/NX-4608/NX4608_11_174965.pdf b/docs/NX-4608/NX4608_11_174965.pdf new file mode 100644 index 0000000..6afaff6 Binary files /dev/null and b/docs/NX-4608/NX4608_11_174965.pdf differ diff --git a/docs/NX-4608/PCB Layout.jpg b/docs/NX-4608/PCB Layout.jpg new file mode 100644 index 0000000..4f747f5 Binary files /dev/null and b/docs/NX-4608/PCB Layout.jpg differ