-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathNexaCommand.h
333 lines (298 loc) · 8.42 KB
/
NexaCommand.h
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
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
#ifndef NEXA_NODE_NEXA_COMMAND_H
#define NEXA_NODE_NEXA_COMMAND_H
#include "Macros.h"
#include "RF433Transceiver.h"
#include "RingBuffer.h"
#include "HexUtils.h"
#include <Arduino.h>
/*
* Encapsulation of a single NexaCommand, with functionality for parsing
* from various formats, and transmitting to a RF433Transceiver instance.
*/
class NexaCommand {
public: // types & constants
enum Version {
NEXA_INVAL, // Unknown/invalid version
NEXA_12BIT, // Old 12-bit command format: DDDDDDDD011S
NEXA_32BIT, // New 32-bit command format: D{24}10GSCCCC
NEXA_END // End sentinel
};
// length of command string on format "V:DDDDDD:G:C:S"
static const size_t cmd_str_len = 14;
public: // initializers
/*
* Initialize NexaCommand instance from incoming command string
* of the form "V:DDDDDD:G:C:S", where
* - V = Nexa command version (hex digit)
* - DDDDDD = 24-bit device id in hex
* - G = group bit (0/1)
* - C = channel in hex (0-F)
* - S = state bit (0/1 == off/on)
*
* Return true on success, false if command string is not valid.
*/
static bool from_cmd_str(NexaCommand & cmd,
const char * buf, size_t len);
/*
* Initialize NexaCommand instance from bits in ring buffer.
*
* This factory will consume (some, but not necessarily all)
* available data in the given ring buffer, and if a complete Nexa
* command is parsed, the given NexaCommand instance will be
* initialized accrodingly.
*
* This factory will not initalize the given NexaCommand instance
* every time it's called, but when it does, it will return true.
*/
static bool from_bit_buffer(NexaCommand & cmd,
RingBuffer<char> & rx_bits);
public: // queries
// Print this Nexa command on the serial port.
void print(Print & out) const;
/*
* Transmit this Nexa command on the given RF transmitter.
*/
void transmit(RF433Transceiver & rf_port, size_t reps = 1) const;
private: // helpers
void tx_12bit(RF433Transceiver & rf_port, size_t reps) const;
void tx_32bit(RF433Transceiver & rf_port, size_t reps) const;
/*
* Convert the given array of 8 '0' or '1' characters into a byte.
*
* The character-encoded 'bits' are interpreted as LSB-first, and
* the corresponding byte value is returned.
*/
static byte charbits2byte(const char bits[8]);
/*
* Initialize this object from the 12/32 bits in the given buf.
*
* No input validation is performed. The given buf is assumed to
* contain 12/32 '0' or '1' characters.
*
* The command bits are of the form:
* - 12-bit format: DDDDDDDD011S
* - 32-bit format: DDDDDDDDDDDDDDDDDDDDDDDD10GSCCCC
*/
void from_12bit_cmd(const char buf[12]);
void from_32bit_cmd(const char buf[32]);
public: // representation
Version version;
byte device[3]; // 24-bit device id (NEXA_12BIT only uses last 8)
byte channel; // 4-bit channel id (always 0 for NEXA_12BIT)
bool group; // group bit (always false for NEXA_12BIT)
bool state; // ON - true, OFF - false
};
bool NexaCommand::from_cmd_str(NexaCommand & cmd,
const char * buf, size_t len)
{
if (len != cmd_str_len)
return false;
if (buf[1] != ':' || buf[8] != ':' || buf[10] != ':' ||
buf[12] != ':')
return false;
int v = Hex::parse_digit(buf[0]);
bool d = Hex::hex2bytes(cmd.device, buf + 2, 3);
int g = Hex::parse_digit(buf[9]);
int c = Hex::parse_digit(buf[11]);
int s = Hex::parse_digit(buf[13]);
if ((v <= NEXA_INVAL && v >= NEXA_END) || !d ||
(g != 1 && g != 0) || c == -1 || (s != 1 && s != 0))
return false;
cmd.version = (Version) v;
cmd.channel = c;
cmd.group = g;
cmd.state = s;
return true;
}
bool NexaCommand::from_bit_buffer(NexaCommand & cmd,
RingBuffer<char> & rx_bits)
{
static Version version = NEXA_INVAL;
static char buf[32]; // Long enough for the longest command
static size_t buf_pos = 0;
static size_t expect = 0;
while (!rx_bits.r_empty()) {
char b = rx_bits.r_pop();
if (b == 'A' || b == 'B') {
buf_pos = 0;
if (b == 'A') {
version = NEXA_32BIT;
expect = 32;
}
else {
version = NEXA_12BIT;
expect = 12;
}
}
else if ((b == '0' || b == '1') && buf_pos < expect)
buf[buf_pos++] = b;
if (expect && buf_pos == expect) { // all bits present
if (version == NEXA_12BIT)
cmd.from_12bit_cmd(buf);
else if (version == NEXA_32BIT)
cmd.from_32bit_cmd(buf);
expect = 0;
buf_pos = 0;
version = NEXA_INVAL;
return true;
}
}
return false;
}
void NexaCommand::print(Print & out) const
{
const size_t device_bytes = 3;
byte device_id[device_bytes * 2];
Hex::bytes2hex((char *) device_id, device, device_bytes);
out.print(version, HEX);
out.print(':');
out.write(device_id, ARRAY_LENGTH(device_id));
out.print(':');
out.print(group ? '1' : '0');
out.print(':');
out.print(channel, HEX);
out.print(':');
out.println(state ? '1' : '0');
}
void NexaCommand::transmit(RF433Transceiver & rf_port, size_t reps) const
{
if (version == NEXA_12BIT)
tx_12bit(rf_port, reps);
else
tx_32bit(rf_port, reps);
#if DEBUG
Serial.print(F("Transmitted code: "));
print(Serial);
#endif
}
void NexaCommand::tx_12bit(RF433Transceiver & rf_port, size_t reps) const
{
/*
* SYNC: SHORT (0.35ms) HIGH, XXLONG (10.9ms) LOW
* '0' bit: SHORT HIGH, LONG (1.05ms) LOW, SHORT HIGH, LONG LOW
* '1' bit: SHORT HIGH, LONG LOW, LONG HIGH, SHORT LOW
*/
enum PulseLength { // in µs
SHORT = 350,
LONG = 3 * 350,
XXLONG = 31 * 350,
};
int bits[12]; // DDDDDDDD011S
// The 8 device bits are device[2] LSB first
for (size_t i = 0; i < 8; ++i)
bits[i] = device[2] >> i & 1;
bits[8] = 0;
bits[9] = 1;
bits[10] = 1;
bits[11] = state;
for (size_t i = 0; i < reps; ++i) {
// SYNC
rf_port.transmit(HIGH, SHORT);
rf_port.transmit(LOW, XXLONG);
// data bits
for (size_t i = 0; i < ARRAY_LENGTH(bits); ++i) {
if (bits[i]) { // '1'
rf_port.transmit(HIGH, SHORT);
rf_port.transmit(LOW, LONG);
rf_port.transmit(HIGH, LONG);
rf_port.transmit(LOW, SHORT);
}
else { // '0'
rf_port.transmit(HIGH, SHORT);
rf_port.transmit(LOW, LONG);
rf_port.transmit(HIGH, SHORT);
rf_port.transmit(LOW, LONG);
}
}
}
rf_port.transmit(HIGH, SHORT);
rf_port.transmit(LOW);
}
void NexaCommand::tx_32bit(RF433Transceiver & rf_port, size_t reps) const
{
/*
* SYNC: XXLONG (10.15ms) LOW, SHORT (0.31ms) HIGH,
* XLONG (2.64ms) LOW, SHORT HIGH
* '0' bit: XSHORT (0.22ms) LOW, SHORT HIGH,
* LONG (1.24ms) LOW, SHORT HIGH
* '1' bit: LONG LOW, SHORT HIGH,
* XSHORT LOW, SHORT HIGH
*/
enum PulseLength { // in µs
XSHORT = 215,
SHORT = 310,
LONG = 1236,
XLONG = 2643,
XXLONG = 10150,
};
int bits[32]; // DDDDDDDDDDDDDDDDDDDDDDDD10GSCCCC
// The 24 device bits are device[2..0] LSB first
for (size_t i = 0; i < 24; ++i)
bits[i] = device[2 - i / 8] >> (i % 8) & 1;
bits[24] = 1;
bits[25] = 0;
bits[26] = group;
bits[27] = state;
bits[28] = channel & B1000;
bits[29] = channel & B100;
bits[30] = channel & B10;
bits[31] = channel & B1;
for (size_t i = 0; i < reps; ++i) {
// SYNC
rf_port.transmit(LOW, XXLONG);
rf_port.transmit(HIGH, SHORT);
rf_port.transmit(LOW, XLONG);
rf_port.transmit(HIGH, SHORT);
// data bits
for (size_t i = 0; i < ARRAY_LENGTH(bits); ++i) {
if (bits[i]) { // '1'
rf_port.transmit(LOW, LONG);
rf_port.transmit(HIGH, SHORT);
rf_port.transmit(LOW, XSHORT);
rf_port.transmit(HIGH, SHORT);
}
else { // '0'
rf_port.transmit(LOW, XSHORT);
rf_port.transmit(HIGH, SHORT);
rf_port.transmit(LOW, LONG);
rf_port.transmit(HIGH, SHORT);
}
}
}
rf_port.transmit(LOW);
}
byte NexaCommand::charbits2byte(const char bits[8])
{
return (bits[7] == '1' ? 1 : 0) << 7 |
(bits[6] == '1' ? 1 : 0) << 6 |
(bits[5] == '1' ? 1 : 0) << 5 |
(bits[4] == '1' ? 1 : 0) << 4 |
(bits[3] == '1' ? 1 : 0) << 3 |
(bits[2] == '1' ? 1 : 0) << 2 |
(bits[1] == '1' ? 1 : 0) << 1 |
(bits[0] == '1' ? 1 : 0);
}
void NexaCommand::from_12bit_cmd(const char buf[12])
{
version = NEXA_12BIT;
device[0] = 0;
device[1] = 0;
device[2] = charbits2byte(buf);
channel = 0;
group = 0;
state = buf[11] == '1';
}
void NexaCommand::from_32bit_cmd(const char buf[32])
{
version = NEXA_32BIT;
device[0] = charbits2byte(buf + 16);
device[1] = charbits2byte(buf + 8);
device[2] = charbits2byte(buf);
channel = (buf[28] == '1' ? B1000 : 0) |
(buf[29] == '1' ? B100 : 0) |
(buf[30] == '1' ? B10 : 0) |
(buf[31] == '1' ? B1 : 0);
group = buf[26] == '1';
state = buf[27] == '1';
}
#endif