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