Skip to content

Commit c0710f2

Browse files
committed
Data types, write quota, better stats
1 parent e604785 commit c0710f2

File tree

8 files changed

+295
-206
lines changed

8 files changed

+295
-206
lines changed

README.md

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -54,14 +54,13 @@ The controller has a built-in web interface. You can use this web interface to c
5454
# Hardware
5555
Get the hardware and connect together:
5656

57-
* **Arduino Uno**.
58-
59-
* **Ethernet shield (with W5100, W5200 or W5500 chip)**. The ubiquitous W5100 shield for Uno is sufficient. !!! ENC28J60 will not work !!!
60-
* **Custom P1P2 Uno adapter**. You can [solder your own adapter](https://github.com/Arnold-n/P1P2Serial/tree/main/circuits#p1p2-adapter-as-arduino-uno-hat) or buy one from Arnold-n (his e-mail address can be found on line 3 of his library [P1P2Serial.cpp](https://github.com/Arnold-n/P1P2Serial/blob/main/P1P2Serial.cpp)).
57+
* **Arduino Uno**.<br>Cheap clones are sufficient.
58+
* **Ethernet shield with WIZnet chip (W5100, W5200 or W5500)**.<br>The ubiquitous W5100 shield for Uno/Mega is sufficient. If available, I recommend W5500 Ethernet Shield. You can also use combo board MCU + ethernet (such as ATmega328 + W5500 board from Keyestudio).<br>ATTENTION: Ethernet shields with ENC28J60 chip will not work !!!
59+
* **Custom P1P2 Uno adapter**.<br>You can [solder your own adapter](https://github.com/Arnold-n/P1P2Serial/tree/main/circuits#p1p2-adapter-as-arduino-uno-hat) or buy one from Arnold-n (his e-mail address can be found on line 3 of his library [P1P2Serial.cpp](https://github.com/Arnold-n/P1P2Serial/blob/main/P1P2Serial.cpp)).
6160

6261
Here is my HW setup (cheap Arduino Uno clone + W5500 Ethernet shield from Keyestudio + custom P1P2 Uno adapter):
6362

64-
<img src="pics/HW.jpg" alt="01" style="zoom:100%;" />
63+
<img src="pics/HW.jpg" alt="HW" style="zoom:100%;" />
6564

6665
# Firmware
6766

@@ -80,19 +79,19 @@ This controller has a built-in webserver which allows you to configure the contr
8079

8180
## System Info
8281

83-
<img src="pics/daikin1.png" alt="01" style="zoom:100%;" />
82+
<img src="pics/daikin1.png" alt="daikin1" style="zoom:100%;" />
8483

8584
**Load Default Settings**. Loads default settings (see DEFAULT_CONFIG in advanced settings). MAC address is retained.
8685

8786
**Reboot**.
8887

8988
**EEPROM Health**. Keeps track of EEPROM write cycles (this counter is persistent, never cleared during factory resets). Replace your Arduino once you reach 100 000 write cycles (with 6 hours EEPROM_INTERVAL you have more than 50 years lifespan).
9089

91-
**Generate New MAC**. Generate new MAC address. First 3 bytes are fixed 90:A2:DA, remaining 3 bytes are true random.
90+
**MAC Address**. First 3 bytes are fixed 90:A2:DA, remaining 3 bytes are random. You can also set manual MAC in IP Settings.
9291

9392
## P1P2 Status
9493

95-
<img src="pics/daikin2.png" alt="02" style="zoom:100%;" />
94+
<img src="pics/daikin2.png" alt="daikin2" style="zoom:100%;" />
9695

9796
**Controller**. Shows you the status of the P1/P2 connection and allows you to manually connect (or disconnect) the controller. The status can be:
9897
* **Disabled**. Controller mode has been disabled in **P1P2 Settings**. The controller can not write to the P1/P2 bus but it still passively monitors the P1/P2 bus and sends (most) data from the heat pump via UDP messages.
@@ -123,8 +122,14 @@ This controller has a built-in webserver which allows you to configure the contr
123122
* **Value**. Parameter value, the number of bytes differs for various packet types. See PACKET_PARAM_VAL_SIZE in advanced settings for the correct number of bytes. Value is also **<ins>in little endian format</ins>**!
124123

125124
**P1P2 Packets**. Counters for packets read from the P1/P2 bus or written to the P1/P2 bus, counters for various read and write errors. If any of the counters rolls over the unsigned long maximum (4,294,967,295), all counters will reset to 0.
126-
* **Read OK**. Number of packets read from the P1/P2 bus, without errors. Not all of them are sent via UDP (see the **Packet Filter** settings). Packets are read from the P1/P2 bus (and sent via UDP) even if the controller is not connected to the P1/P2 bus.
127-
* **Write OK**. Number of packets written to the P1/P2 bus. Includes both packets written automatically by the controller (requests for the counters packets), write commands from the web interface (**Write Packet**) and write commands received via UDP. Writing to the P1/P2 bus is only possible if the controller is connected to the P1/P2 bus (to the main Daikin controller).
125+
* **Bus Read OK**. Number of packets read from the P1/P2 bus, without errors. Not all of them are sent via UDP (see the **Packet Filter** settings). Packets are read from the P1/P2 bus (and sent via UDP) even if the controller is not connected to the P1/P2 bus.
126+
* **Bus Write OK**. Number of packets written to the P1/P2 bus. Includes both packets written automatically by the controller (requests for the counters packets), write commands from the web interface and write commands received via UDP. Writing to the P1/P2 bus is only possible if the controller is connected to the P1/P2 bus (to the main Daikin controller).
127+
* **EEPROM Write Quota Reached**. Daily EEPROM Write Quota (configured in **P1P2 Settings**) was reached. The command (received via UDP or from the web interface) was dropped.
128+
* **Write Queue Full**. Internal queue (circular buffer) for commands is full. The command (received via UDP or from the web interface) was dropped.
129+
* **Write Command Invalid**. Command received via UDP or from the web interface was invalid, it was dropped. Possible reasons:
130+
- Packet type (first byte) is not supported (PACKET_PARAM_VAL_SIZE in advanced settings is set to zero).
131+
- Incorrect packet length. Command should have 1 byte for type, 2 bytes for parameter number and the correct numer of bytes for the parameter value (see PACKET_PARAM_VAL_SIZE in advanced settings).
132+
128133
* **Parity Read Error**.
129134
* **Too Long Read Error**. Packet received is longer than the read buffer.
130135
* **Start Bit Write Error**. Start bit error during write.
@@ -134,15 +139,13 @@ This controller has a built-in webserver which allows you to configure the contr
134139

135140
**UDP Messages**.\*\*
136141
* **Sent to UDP**. Counts packets (messages) read from the P1/P2 bus and sent via UDP. Not all packets read from the P1/P2 bus are sent via UDP (see the **Packet Filter** settings).
137-
* **Received from UDP**. Counts valid messages received via UDP. Messages are validated:
138-
- Packet type (first byte) is supported (PACKET_PARAM_VAL_SIZE in advanced settings is not zero).
139-
- Correct packet length. 1 byte for type, 2 bytes for parameter number and the correct numer of param vallue bytes (see PACKET_PARAM_VAL_SIZE in advanced settings).
140-
- The internal queue (circular buffer) for commands is not full.
141-
142+
* **Received from UDP**. Counts all messages received via UDP from a valid remote IP.
142143

143144
## IP Settings
144145

145-
<img src="pics/daikin3.png" alt="02" style="zoom:100%;" />
146+
<img src="pics/daikin3.png" alt="daikin3" style="zoom:100%;" />
147+
148+
**MAC Address**.\*\* Change MAC address. "Randomize" button will generate new random MAC (first 3 bytes fixed 90:A2:DA, last 3 bytes will be random).
146149

147150
**Auto IP**.\* Once enabled, Arduino will receive IP, gateway, subnet and DNS from the DHCP server.
148151

@@ -156,7 +159,7 @@ This controller has a built-in webserver which allows you to configure the contr
156159

157160
## TCP/UDP Settings
158161

159-
<img src="pics/daikin4.png" alt="02" style="zoom:100%;" />
162+
<img src="pics/daikin4.png" alt="daikin4" style="zoom:100%;" />
160163

161164
**Remote IP**. IP address of your home automation system which listens for UDP messages and sends UDP commands.
162165

@@ -170,7 +173,7 @@ This controller has a built-in webserver which allows you to configure the contr
170173

171174
## P1P2 Settings
172175

173-
<img src="pics/daikin5.png" alt="05" style="zoom:100%;" />
176+
<img src="pics/daikin5.png" alt="daikin5" style="zoom:100%;" />
174177

175178
**Controller (Write to P1P2)**.
176179
* **Disabled** (safe). The controller is permanently disconnected from the P1/P2 bus, manual connection is not possible. The controller can not write to the P1/P2 bus but it still passively monitors the P1/P2 bus and sends (most) data from the heat pump via UDP. Disable the controller in case you experience persistent connection failures and/or write errors.
@@ -185,7 +188,7 @@ This controller has a built-in webserver which allows you to configure the contr
185188

186189
## Packet Filter
187190

188-
<img src="pics/daikin6.png" alt="06" style="zoom:100%;" />
191+
<img src="pics/daikin6.png" alt="daikin6" style="zoom:100%;" />
189192

190193
The **Packet Filter** page lists all packet types observed on the P1/P2 bus. Some of them are exchanged between the heat pump and the main Daikin controller, others are exchanged between our controller (or other external controllers) and the main Daikin controller. If you do not see any packet types, wait few seconds. If a new packet type is detected, it will be automatically added to the list. **Packet types enabled on this page are forwarded via UDP**. By default, only Counter Packet (0xB8) and Data Packets (usually 0x10 - 0x16) are sent via UDP. Enable additional packet types if you want to test or reverse-engineer the P1/P2 protocol.
191194

arduino-altherma-controller/01-interfaces.ino

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/* *******************************************************************
2-
Ethernet and serial interface functions
2+
Ethernet interface functions
33
44
startEthernet()
55
- initiates ethernet interface
@@ -18,15 +18,15 @@
1818
maintainCounters(), rollover()
1919
- synchronizes roll-over of data counters to zero
2020
21-
resetStats()
22-
- resets Modbus stats
21+
resetStats(), resetEepromStats()
22+
- resets P1P2 stats and Daikin EEPROM stats
2323
2424
generateMac()
2525
- generate random MAC using pseudo random generator (faster and than build-in random())
2626
2727
manageSockets()
2828
- closes sockets which are waiting to be closed or which refuse to close
29-
- forwards sockets with data available (webserver or Modbus TCP) for further processing
29+
- forwards sockets with data available for further processing by the webserver
3030
- disconnects (closes) sockets which are too old / idle for too long
3131
- opens new sockets if needed (and if available)
3232
@@ -47,11 +47,7 @@ void startEthernet() {
4747
delay(25);
4848
digitalWrite(ETH_RESET_PIN, HIGH);
4949
delay(ETH_RESET_DELAY);
50-
pinMode(ETH_RESET_PIN, INPUT);
5150
}
52-
byte mac[6];
53-
memcpy(mac, MAC_START, 3); // set first 3 bytes
54-
memcpy(mac + 3, localConfig.macEnd, 3); // set last 3 bytes
5551
#ifdef ENABLE_DHCP
5652
if (localConfig.enableDhcp) {
5753
dhcpSuccess = Ethernet.begin(mac);
@@ -72,10 +68,12 @@ void startEthernet() {
7268
#endif
7369
}
7470

71+
void (*resetFunc)(void) = 0; //declare reset function at address 0
72+
7573
#ifdef ENABLE_DHCP
7674
void maintainDhcp() {
7775
if (localConfig.enableDhcp && dhcpSuccess == true) { // only call maintain if initial DHCP request by startEthernet was successfull
78-
uint8_t maintainResult = Ethernet.maintain();
76+
byte maintainResult = Ethernet.maintain();
7977
if (maintainResult == 1 || maintainResult == 3) { // renew failed or rebind failed
8078
dhcpSuccess = false;
8179
startEthernet(); // another DHCP request, fallback to static IP
@@ -86,7 +84,7 @@ void maintainDhcp() {
8684

8785
#ifdef ENABLE_EXTRA_DIAG
8886
void maintainUptime() {
89-
unsigned long milliseconds = millis();
87+
uint32_t milliseconds = millis();
9088
if (last_milliseconds > milliseconds) {
9189
//in case of millis() overflow, store existing passed seconds
9290
remaining_seconds = seconds;
@@ -100,7 +98,7 @@ void maintainUptime() {
10098
}
10199
#endif /* ENABLE_EXTRA_DIAG */
102100

103-
const unsigned long ROLLOVER = 0xFFFFFF00;
101+
const uint32_t ROLLOVER = 0xFFFFFF00;
104102
bool rollover() {
105103
// synchronize roll-over of run time, data counters and modbus stats to zero, at 0xFFFFFF00
106104
for (byte i = 0; i < P1P2_LAST; i++) {
@@ -143,20 +141,23 @@ void generateMac() {
143141
seed1 = 36969L * (seed1 & 65535L) + (seed1 >> 16);
144142
seed2 = 18000L * (seed2 & 65535L) + (seed2 >> 16);
145143
uint32_t randomBuffer = (seed1 << 16) + seed2; /* 32-bit random */
144+
memcpy(mac, MAC_START, 3); // set first 3 bytes
146145
for (byte i = 0; i < 3; i++) {
147-
localConfig.macEnd[i] = randomBuffer & 0xFF;
146+
mac[i + 3] = randomBuffer & 0xFF; // random last 3 bytes
148147
randomBuffer >>= 8;
149148
}
150149
}
151150

152151
void updateEeprom() {
153152
eepromTimer.sleep(EEPROM_INTERVAL * 60UL * 60UL * 1000UL); // EEPROM_INTERVAL is in hours, sleep is in milliseconds!
154153
eepromCount.eepromWrites++; // we assume that at least some bytes are written to EEPROM during EEPROM.update or EEPROM.put
155-
int address = CONFIG_START;
154+
byte address = CONFIG_START;
156155
EEPROM.put(address, eepromCount);
157156
address += sizeof(eepromCount);
158157
EEPROM.put(address, VERSION[0]);
159158
address += 1;
159+
EEPROM.put(address, mac);
160+
address += 6;
160161
EEPROM.put(address, localConfig);
161162
address += sizeof(localConfig);
162163
EEPROM.put(address, p1p2Count);
@@ -167,9 +168,9 @@ void updateEeprom() {
167168
}
168169

169170
#if MAX_SOCK_NUM == 8
170-
unsigned long lastSocketUse[MAX_SOCK_NUM] = { 0, 0, 0, 0, 0, 0, 0, 0 }; // +rs 03Feb2019 - records last interaction involving each socket to enable detecting sockets unused for longest time period
171+
uint32_t lastSocketUse[MAX_SOCK_NUM] = { 0, 0, 0, 0, 0, 0, 0, 0 };
171172
#elif MAX_SOCK_NUM == 4
172-
unsigned long lastSocketUse[MAX_SOCK_NUM] = { 0, 0, 0, 0 }; // +rs 03Feb2019 - records last interaction involving each socket to enable detecting sockets unused for longest time period
173+
uint32_t lastSocketUse[MAX_SOCK_NUM] = { 0, 0, 0, 0 };
173174
#endif
174175

175176
// from https://github.com/SapientHetero/Ethernet/blob/master/src/socket.cpp
@@ -302,12 +303,14 @@ void manageController() {
302303
cmdQueue.push(0);
303304
indoorInQueue = true;
304305
}
306+
#ifdef ENABLE_EXTRA_DIAG
305307
if (daikinOutdoor[0] == '\0' && outdoorInQueue == false) {
306308
cmdQueue.push(2);
307309
cmdQueue.push(PACKET_TYPE_OUTDOOR_NAME);
308310
cmdQueue.push(0);
309311
outdoorInQueue = true;
310312
}
313+
#endif /* ENABLE_EXTRA_DIAG */
311314
break;
312315
default:
313316
break;

arduino-altherma-controller/02-UDP.ino

Lines changed: 26 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
/* *******************************************************************
2-
Modbus TCP/UDP functions
2+
UDP functions
33
44
recvUdp()
55
- receives P1P2 command via UDP
6-
- calls checkRequest
6+
- calls checkCommand
77
88
checkCommand()
9-
- checks P1P2 command (correct MBAP header, CRC in case of Modbus RTU over TCP/UDP)
9+
- checks P1P2 command
1010
- checks availability of queue
11-
- stores requests into queue or returns an error
11+
- stores commands into queue or returns an error
1212
1313
deleteCmd()
1414
- deletes command from queue
@@ -21,39 +21,42 @@
2121
2222
***************************************************************** */
2323

24-
uint8_t masks[8] = { 1, 2, 4, 8, 16, 32, 64, 128 };
24+
byte masks[8] = { 1, 2, 4, 8, 16, 32, 64, 128 };
2525

2626
void recvUdp() {
27-
unsigned int udpLen = Udp.parsePacket();
27+
uint16_t udpLen = Udp.parsePacket();
2828
if (udpLen) {
2929
byte command[1 + 2 + MAX_PARAM_SIZE]; // 1 byte packet type + 2 bytes param number + MAX_PARAM_SIZE bytes param value
3030
if (udpLen > sizeof(command) || (!localConfig.udpBroadcast && Udp.remoteIP() != IPAddress(localConfig.remoteIp))) {
3131
while (Udp.available()) Udp.read();
32-
// TODO error
32+
// TODO error: UDP too long or wrong remote IP
3333
return;
3434
}
3535
Udp.read(command, sizeof(command));
3636
checkCommand(command, byte(udpLen));
37+
#ifdef ENABLE_EXTRA_DIAG
38+
udpCount[UDP_RECEIVED]++;
39+
#endif /* ENABLE_EXTRA_DIAG */
3740
}
3841
}
3942

4043
void checkCommand(byte command[], byte cmdLen) {
41-
if (cmdQueue.available() > cmdLen // check available space in queue
42-
&& PACKET_PARAM_VAL_SIZE[command[0] - PACKET_TYPE_CONTROL[FIRST]] != 0 // check if param size is not zero (0 = write command not supported (yet))
43-
&& cmdLen - 3 == (PACKET_PARAM_VAL_SIZE[command[0] - PACKET_TYPE_CONTROL[FIRST]]) // check parameter value size
44-
&& (command[0] >= PACKET_TYPE_CONTROL[FIRST] && command[0] <= PACKET_TYPE_CONTROL[LAST]) // check packet type
45-
&& changed36Param(command) == true) {
46-
#ifdef ENABLE_EXTRA_DIAG
47-
udpCount[UDP_RECEIVED]++;
48-
#endif /* ENABLE_EXTRA_DIAG */ // check hysteresis for packet type 0x36
49-
// push to queue (incl. cmdLen)
50-
cmdQueue.push(cmdLen); // first byte in queue is cmdLen
51-
for (byte i = 0; i < cmdLen; i++) {
52-
cmdQueue.push(command[i]);
53-
Serial.println(hex(command[i]));
44+
if (cmdQueue.available() > cmdLen) { // check available space in queue
45+
if (PACKET_PARAM_VAL_SIZE[command[0] - PACKET_TYPE_CONTROL[FIRST]] != 0 // check if param size is not zero (0 = write command not supported (yet))
46+
&& cmdLen - 3 == (PACKET_PARAM_VAL_SIZE[command[0] - PACKET_TYPE_CONTROL[FIRST]]) // check parameter value size
47+
&& (command[0] >= PACKET_TYPE_CONTROL[FIRST] && command[0] <= PACKET_TYPE_CONTROL[LAST])) { // check packet type
48+
if (changed36Param(command) == true) {
49+
// push to queue (incl. cmdLen)
50+
cmdQueue.push(cmdLen); // first byte in queue is cmdLen
51+
for (byte i = 0; i < cmdLen; i++) {
52+
cmdQueue.push(command[i]);
53+
}
54+
}
55+
} else {
56+
p1p2Count[P1P2_WRITE_INVALID]++; // Write Command Invalid
5457
}
5558
} else {
56-
// TODO error
59+
p1p2Count[P1P2_WRITE_QUEUE]++; // TODO error: Write Queue Full
5760
}
5861
}
5962

@@ -65,11 +68,11 @@ void deleteCmd() // delete command from queue
6568
}
6669
}
6770

68-
bool getPacketStatus(const uint8_t packetType, const byte status) {
71+
bool getPacketStatus(const byte packetType, const byte status) {
6972
return (localConfig.packetStatus[status][packetType / 8] & masks[packetType & 7]) > 0;
7073
}
7174

72-
bool setPacketStatus(const uint8_t packetType, byte status, const bool value) {
75+
bool setPacketStatus(const byte packetType, byte status, const bool value) {
7376
if (getPacketStatus(packetType, status) == value) return false;
7477
if (value == 0) {
7578
localConfig.packetStatus[status][packetType / 8] &= ~masks[packetType & 7];

0 commit comments

Comments
 (0)