-
-
Notifications
You must be signed in to change notification settings - Fork 4
/
Max.ino
263 lines (215 loc) · 5.9 KB
/
Max.ino
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
#include "Max.h"
#include <TStreaming.h>
#ifdef ETHERNET
#include <Ethernet.h>
#endif
#ifdef LCD_I2C
#include <LiquidCrystal_I2C.h>
#endif // LCD_I2C
#include "Crc.h"
#include "Util.h"
#include "MaxRF22.h"
#include "MaxRFProto.h"
#include "Pn9.h"
static_assert(PN9_LEN >= RF22_MAX_MESSAGE_LEN, "Not enough pn9 bytes defined");
MaxRF22 rf(9);
#ifdef LCD_I2C
#define LCD_ADDR 0x20
#define LCD_COLS 20
#define LCD_ROWS 4
LiquidCrystal_I2C lcd(LCD_ADDR, LCD_COLS, LCD_ROWS); // set the LCD address to 0x27 for a 20 chars and 4 line display
#else
/* Define the lcd object as a bottomless pit for prints. */
Null lcd;
#endif // LCD_I2C
#ifdef KETTLE_RELAY_PIN
bool kettle_status;
#endif // KETTLE_RELAY_PIN
#ifdef ETHERNET
EthernetServer server = EthernetServer(1234); //port 80
DoublePrint p = (Serial & server);
#else
Print &p = Serial;
#endif
void printStatus() {
#ifdef LCD_I2C
int row = LCD_ROWS - 1;
lcd.clear();
#endif
for (int i = 0; i < lengthof(devices); ++i) {
Device *d = &devices[i];
if (!d->address) break;
if (d->type != DeviceType::RADIATOR && d->type != DeviceType::WALL) continue;
#ifdef LCD_I2C
lcd.setCursor(0, row--);
#endif
if (d->name) {
(p & lcd) << d->name;
} else {
/* Only print two bytes on the lcd to save space */
lcd << V<HexBits<16>>(d->address);
p << V<Address>(d->address);
}
(p & lcd) << " " << V<ActualTemp>(d->actual_temp)
<< "/" << V<SetTemp>(d->set_temp);
if (d->type == DeviceType::RADIATOR)
(p & lcd) << " " << V<ValvePos>(d->data.radiator.valve_pos);
p << endl;
}
p << endl;
#ifdef LCD_I2C
#ifdef KETTLE_RELAY_PIN
lcd.home();
(p & lcd) << F("Kettle: ") << (kettle_status ? F("On") : F("Off"));
#endif // KETTLE_RELAY_PIN
#endif // LCD_I2C
p << endl;
/* Print machine-parseable status line (to draw pretty graphs) */
p << "STATUS\t" << millis() << "\t";
for (int i = 0; i < lengthof(devices); ++i) {
Device *d = &devices[i];
if (!d->address) break;
if (d->type != DeviceType::RADIATOR && d->type != DeviceType::WALL) continue;
p << V<ActualTemp>(d->actual_temp) << "\t" << V<SetTemp>(d->set_temp) << "\t";
if (d->type == DeviceType::RADIATOR)
p << V<ValvePos>(d->data.radiator.valve_pos);
else
p << "NA";
p << "\t";
}
p << (kettle_status ? "1" : "0") << endl;
}
#ifdef KETTLE_RELAY_PIN
void switchKettle() {
uint32_t total = 0;
uint32_t max = 0;
for (int i = 0; i < lengthof(devices); ++i) {
Device *d = &devices[i];
if (!d->address) break;
if (d->type != DeviceType::RADIATOR) continue;
if (d->data.radiator.valve_pos == VALVE_UNKNOWN) continue;
total += d->data.radiator.valve_pos;
if (d->data.radiator.valve_pos > max)
max = d->data.radiator.valve_pos;
}
/* One radiator opened fairly far can turn the kettle on by itself, or
* a few radiators opened a little bit. */
kettle_status = (max > 30 || total > 40);
digitalWrite(KETTLE_RELAY_PIN, kettle_status ? HIGH : LOW);
}
#endif // KETTLE_RELAY_PIN
void dump_buffer(Print &p, uint8_t *buf, uint8_t len) {
/* Dump the raw received data */
int i, j;
for (i = 0; i < len; i += 16)
{
// Hex
for (j = 0; j < 16 && i+j < len; j++)
{
p << V<Hex>(buf[i+j]) << " ";
}
// Padding on last block
while (j++ < 16)
p << " ";
p << " ";
// ASCII
for (j = 0; j < 16 && i+j < len; j++)
p << (isprint(buf[i+j]) ? (char)buf[i+j] : '.');
p << "\r\n";
}
p << "\r\n";
}
void setup()
{
Serial.begin(115200);
if (rf.init())
Serial.println(F("RF init OK"));
else
Serial.println(F("RF init failed"));
#ifdef LCD_I2C
lcd.init();
lcd.backlight();
lcd.home();
lcd.clear();
Serial.println(F("LCD init complete"));
#endif // LCD_I2C
#ifdef KETTLE_RELAY_PIN
pinMode(KETTLE_RELAY_PIN, OUTPUT);
#endif // KETTLE_RELAY_PIN
#ifdef ETHERNET
byte mac[] = ETHERNET_MAC;
if (Ethernet.begin(mac))
p << F("IP: ") << Ethernet.localIP() << "\r\n";
else
p << F("DHCP Failure") << "\r\n";
server.begin();
#endif
p << F("Initialized") << "\r\n";
printStatus();
}
void loop()
{
uint8_t buf[RF22_MAX_MESSAGE_LEN];
uint8_t len = sizeof(buf);
if (Serial.read() != -1) {
Serial.println("OK");
printStatus();
}
#ifdef ETHERNET
EthernetClient c = server.available();
if (c && c.read() != -1) {
c.println("OK");
printStatus();
}
#endif
if (rf.recv(buf, &len))
{
/* Enable reception right away again, so we won't miss the next
* message while processing this one. */
rf.setModeRx();
p << F("Received ") << len << F(" bytes") << "\r\n";
dump_buffer(p, buf, len);
if (len < 3) {
p << F("Invalid packet length (") << len << ")" << "\r\n";
return;
}
/* Dewhiten data */
if (xor_pn9(buf, len) < 0) {
p << F("Invalid packet length (") << len << ")" << "\r\n";
return;
}
p << F("Dewhitened:") << "\r\n";
dump_buffer(p, buf, len);
/* Calculate CRC (but don't include the CRC itself) */
uint16_t crc = calc_crc(buf, len - 2);
if (buf[len - 1] != (crc & 0xff) || buf[len - 2] != (crc >> 8)) {
p << F("CRC error") << "\r\n";
return;
}
/* Parse the message (without length byte and CRC) */
MaxRFMessage *rfm = MaxRFMessage::parse(buf + 1, len - 3);
if (rfm == NULL) {
p << F("Packet is invalid") << "\r\n";
} else {
p << *rfm << "\r\n";
rfm->updateState();
delete rfm;
}
#ifdef KETTLE_RELAY_PIN
switchKettle();
#endif // KETTLE_RELAY_PIN
printStatus();
#if 0
#ifdef LCD_I2C
/* Use the first two rows of the LCD for dumped packet data */
lcd.home();
for (i = 0; i < len && i < LCD_COLS; ++i) {
if (i == LCD_COLS / 2) lcd.setCursor(0, 1);
printHex(NULL, buf[i], BYTE_SIZE, false, &lcd);
}
#endif // LCD_I2C
#endif
p << "\r\n";
}
}
/* vim: set sw=2 sts=2 expandtab filetype=cpp: */