forked from adpeace/thermostat
-
Notifications
You must be signed in to change notification settings - Fork 0
/
thermostat.ino
187 lines (163 loc) · 5.29 KB
/
thermostat.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
#include "rf69.h"
/*
* Setup
*/
#define SERIAL_BAUD 57600
const uint8_t sync_val[] = {0x6c, 0xb6, 0xcb, 0x2c, 0x92, 0xd9};
void setup()
{
Serial.begin(SERIAL_BAUD);
while (!Serial)
;
rf69_init(sizeof sync_val, 2, sync_val, 27);
Serial.println("Danfoss thermostat transceiver");
}
/*
* Packet definition and construction
*/
const static unsigned char thermostat_packet[] = {
0xAA, 0xDD, 0x46, 0x00, 0x00, 0x00
};
/* Note that these positions applied to packets with the sync word included,
* which isn't the case for those received over RF. */
#define THERMOSTAT_ID1_POS 3
#define THERMOSTAT_ID2_POS 4
#define COMMAND_POS 5
#define COMMAND_ON 0x33
#define COMMAND_LEARN 0x77
#define COMMAND_OFF 0xcc
/* out must be an array of length at least 3 * (sizeof thermostat_packet) */
static void mk_danfoss_packet(uint8_t *out, unsigned int thermid, uint8_t cmd)
{
uint8_t packet[sizeof thermostat_packet];
memcpy(packet, thermostat_packet, sizeof packet);
packet[THERMOSTAT_ID1_POS] = thermid & 0xff;
packet[THERMOSTAT_ID2_POS] = (thermid & 0xff00) >> 8;
packet[COMMAND_POS] = cmd;
encode_3b(packet, out, sizeof packet);
}
static void encode_3b(const uint8_t *in, uint8_t *out, size_t insz)
{
unsigned int i;
memset(out, 0, insz * 3);
for (i = 0; i < insz * 8; i++) {
unsigned int inval = in[i / 8] & (1 << (7 - (i % 8))) ? 1 : 0;
unsigned int outbit = i * 3;
/* Set out-value to 011 or 001: first bit already cleared,
* last bit always 1: */
out[(outbit + 1) / 8] |= inval << (7 - ((outbit + 1) % 8));
out[(outbit + 2) / 8] |= 1 << (7 - ((outbit + 2) % 8));
}
}
static void decode_3b(const uint8_t *in, uint8_t *out, size_t outsz)
{
unsigned int i;
memset(out, 0, outsz);
for (i = 0; i < outsz * 8; i++) {
unsigned int inbit = 1 + i * 3;
uint8_t val = (in[inbit / 8] & (1 << (7 - (inbit % 8))))
? 1 : 0;
out[i / 8] |= val << (7 - (i % 8));
}
}
/*
* Command interface
*/
static const char *strcmd(char cmd)
{
switch (cmd) {
case 'O': return "ON";
case 'X': return "OFF";
case 'L': return "LEARN";
default: return "UNKNOWN";
}
}
/* Command is O for on, X for off, L for learn. */
static void issue_command(char command, unsigned int thermid)
{
uint8_t packet[6 * sizeof thermostat_packet];
uint8_t packet_cmd = command == 'O' ? COMMAND_ON
: command == 'X' ? COMMAND_OFF
: command == 'L' ? COMMAND_LEARN : 0;
mk_danfoss_packet(packet, thermid, packet_cmd);
/* Transmit two copies back-to-back */
memcpy(&packet[3 * sizeof thermostat_packet], packet,
3 * sizeof thermostat_packet);
rf69_transmit(packet, sizeof packet, true);
}
static void handle_command(const char *command)
{
long thermostat_id;
switch (toupper(command[0])) {
case 'O': /* on */
case 'X': /* off */
case 'L': /* learn */
thermostat_id = strtol(&command[1], NULL, 16);
if (thermostat_id > 0 && thermostat_id < 0xffff) {
/* valid thermostat ID, issue the command */
Serial.print("ISSUE 0x");
Serial.print(thermostat_id, HEX);
Serial.print(" ");
Serial.println(strcmd(toupper(command[0])));
issue_command(toupper(command[0]), thermostat_id);
}
break;
}
}
/* Build a line of input from serial, handling the command when it's done.
* This avoids having a blocking readline call in loop(), which would
* prevent us from receiving RF messages. */
#define SERIAL_RXBUF_SZ 16
char serial_rxbuf[SERIAL_RXBUF_SZ];
uint8_t serial_rxpos = 0;
static void handle_serial_char(char c)
{
if (serial_rxpos < SERIAL_RXBUF_SZ) {
serial_rxbuf[serial_rxpos++] = c == '\n' ? '\0' : c;
if (c == '\n')
handle_command(serial_rxbuf);
}
/* Command-delimination character always resets command state */
if (c == '\n') {
serial_rxpos = 0;
}
}
void loop()
{
uint8_t data[30];
uint8_t data_sz = sizeof data;
while (Serial.available() > 0) {
int val = Serial.read();
if (val >= 0)
handle_serial_char((char)val);
}
if (rf69_receiveDone(data, &data_sz)) {
uint8_t decoded[9], i;
uint16_t thermostat_id;
decode_3b(data, decoded, 9);
/* Second half of message is repeated but with extra bit in:
* get rid of it so we should have two identical messages: */
for (i = 3; i < 9; i++) {
decoded[i] = decoded[i] << 1;
if (i < 8)
decoded[i] |= decoded[i + 1] >> 7;
}
/* Now decode it: check for message consistency */
if (decoded[3] != 0xAA ||
decoded[4] != 0xDD ||
decoded[5] != 0x46 ||
decoded[0] != decoded[6] ||
decoded[1] != decoded[7])
return;
/* Check the thermostat ID */
thermostat_id = (decoded[1] << 8) | decoded[0];
Serial.print("RECV 0x");
Serial.print(thermostat_id, HEX);
if (decoded[2] == COMMAND_OFF)
Serial.println(" OFF");
else if (decoded[2] == COMMAND_LEARN)
Serial.println(" LEARN");
else if (decoded[2] == COMMAND_ON)
Serial.println(" ON");
}
}