root / RF12 / examples / RF12demo / RF12demo.pde
History | View | Annotate | Download (22.2 KB)
| 1 | //>>> The latest version of this code can be found at https://github.com/jcw/ !! |
|---|---|
| 2 | |
| 3 | // Configure some values in EEPROM for easy config of the RF12 later on. |
| 4 | // 2009-05-06 <jcw@equi4.com> http://opensource.org/licenses/mit-license.php |
| 5 | // $Id: RF12demo.pde 7763 2011-12-11 01:28:16Z jcw $ |
| 6 | |
| 7 | // this version adds flash memory support, 2009-11-19 |
| 8 | |
| 9 | #include <Ports.h> |
| 10 | #include <RF12.h> |
| 11 | #include <util/crc16.h> |
| 12 | #include <util/parity.h> |
| 13 | #include <avr/eeprom.h> |
| 14 | #include <avr/pgmspace.h> |
| 15 | |
| 16 | #define DATAFLASH 1 // check for presence of DataFlash memory on JeeLink |
| 17 | #define FLASH_MBIT 16 // support for various dataflash sizes: 4/8/16 Mbit |
| 18 | |
| 19 | #define LED_PIN 9 // activity LED, comment out to disable |
| 20 | |
| 21 | #define COLLECT 0x20 // collect mode, i.e. pass incoming without sending acks |
| 22 | |
| 23 | static unsigned long now () {
|
| 24 | // FIXME 49-day overflow |
| 25 | return millis() / 1000; |
| 26 | } |
| 27 | |
| 28 | static void activityLed (byte on) {
|
| 29 | #ifdef LED_PIN |
| 30 | pinMode(LED_PIN, OUTPUT); |
| 31 | digitalWrite(LED_PIN, !on); |
| 32 | #endif |
| 33 | } |
| 34 | |
| 35 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
| 36 | // RF12 configuration setup code |
| 37 | |
| 38 | typedef struct {
|
| 39 | byte nodeId; |
| 40 | byte group; |
| 41 | char msg[RF12_EEPROM_SIZE-4]; |
| 42 | word crc; |
| 43 | } RF12Config; |
| 44 | |
| 45 | static RF12Config config; |
| 46 | |
| 47 | static char cmd; |
| 48 | static byte value, stack[RF12_MAXDATA], top, sendLen, dest, quiet; |
| 49 | static byte testbuf[RF12_MAXDATA], testCounter; |
| 50 | |
| 51 | static void addCh (char* msg, char c) {
|
| 52 | byte n = strlen(msg); |
| 53 | msg[n] = c; |
| 54 | } |
| 55 | |
| 56 | static void addInt (char* msg, word v) {
|
| 57 | if (v >= 10) |
| 58 | addInt(msg, v / 10); |
| 59 | addCh(msg, '0' + v % 10); |
| 60 | } |
| 61 | |
| 62 | static void saveConfig () {
|
| 63 | // set up a nice config string to be shown on startup |
| 64 | memset(config.msg, 0, sizeof config.msg); |
| 65 | strcpy(config.msg, " "); |
| 66 | |
| 67 | byte id = config.nodeId & 0x1F; |
| 68 | addCh(config.msg, '@' + id); |
| 69 | strcat(config.msg, " i"); |
| 70 | addInt(config.msg, id); |
| 71 | if (config.nodeId & COLLECT) |
| 72 | addCh(config.msg, '*'); |
| 73 | |
| 74 | strcat(config.msg, " g"); |
| 75 | addInt(config.msg, config.group); |
| 76 | |
| 77 | strcat(config.msg, " @ "); |
| 78 | static word bands[4] = { 315, 433, 868, 915 };
|
| 79 | word band = config.nodeId >> 6; |
| 80 | addInt(config.msg, bands[band]); |
| 81 | strcat(config.msg, " MHz "); |
| 82 | |
| 83 | config.crc = ~0; |
| 84 | for (byte i = 0; i < sizeof config - 2; ++i) |
| 85 | config.crc = _crc16_update(config.crc, ((byte*) &config)[i]); |
| 86 | |
| 87 | // save to EEPROM |
| 88 | for (byte i = 0; i < sizeof config; ++i) {
|
| 89 | byte b = ((byte*) &config)[i]; |
| 90 | eeprom_write_byte(RF12_EEPROM_ADDR + i, b); |
| 91 | } |
| 92 | |
| 93 | if (!rf12_config()) |
| 94 | Serial.println("config save failed");
|
| 95 | } |
| 96 | |
| 97 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
| 98 | // OOK transmit code |
| 99 | |
| 100 | // Turn transmitter on or off, but also apply asymmetric correction and account |
| 101 | // for 25 us SPI overhead to end up with the proper on-the-air pulse widths. |
| 102 | // With thanks to JGJ Veken for his help in getting these values right. |
| 103 | static void ookPulse(int on, int off) {
|
| 104 | rf12_onOff(1); |
| 105 | delayMicroseconds(on + 150); |
| 106 | rf12_onOff(0); |
| 107 | delayMicroseconds(off - 200); |
| 108 | } |
| 109 | |
| 110 | static void fs20sendBits(word data, byte bits) {
|
| 111 | if (bits == 8) {
|
| 112 | ++bits; |
| 113 | data = (data << 1) | parity_even_bit(data); |
| 114 | } |
| 115 | for (word mask = bit(bits-1); mask != 0; mask >>= 1) {
|
| 116 | int width = data & mask ? 600 : 400; |
| 117 | ookPulse(width, width); |
| 118 | } |
| 119 | } |
| 120 | |
| 121 | static void fs20cmd(word house, byte addr, byte cmd) {
|
| 122 | byte sum = 6 + (house >> 8) + house + addr + cmd; |
| 123 | for (byte i = 0; i < 3; ++i) {
|
| 124 | fs20sendBits(1, 13); |
| 125 | fs20sendBits(house >> 8, 8); |
| 126 | fs20sendBits(house, 8); |
| 127 | fs20sendBits(addr, 8); |
| 128 | fs20sendBits(cmd, 8); |
| 129 | fs20sendBits(sum, 8); |
| 130 | fs20sendBits(0, 1); |
| 131 | delay(10); |
| 132 | } |
| 133 | } |
| 134 | |
| 135 | static void kakuSend(char addr, byte device, byte on) {
|
| 136 | int cmd = 0x600 | ((device - 1) << 4) | ((addr - 1) & 0xF); |
| 137 | if (on) |
| 138 | cmd |= 0x800; |
| 139 | for (byte i = 0; i < 4; ++i) {
|
| 140 | for (byte bit = 0; bit < 12; ++bit) {
|
| 141 | ookPulse(375, 1125); |
| 142 | int on = bitRead(cmd, bit) ? 1125 : 375; |
| 143 | ookPulse(on, 1500 - on); |
| 144 | } |
| 145 | ookPulse(375, 375); |
| 146 | delay(11); // approximate |
| 147 | } |
| 148 | } |
| 149 | |
| 150 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
| 151 | // DataFlash code |
| 152 | |
| 153 | #if DATAFLASH |
| 154 | |
| 155 | #define DF_ENABLE_PIN 8 // PB0 |
| 156 | |
| 157 | #if FLASH_MBIT == 4 |
| 158 | // settings for 0.5 Mbyte flash in JLv2 |
| 159 | #define DF_BLOCK_SIZE 16 // number of pages erased at same time |
| 160 | #define DF_LOG_BEGIN 32 // first 2 blocks reserved for future use |
| 161 | #define DF_LOG_LIMIT 0x0700 // last 64k is not used for logging |
| 162 | #define DF_MEM_TOTAL 0x0800 // 2048 pages, i.e. 0.5 Mbyte |
| 163 | #define DF_DEVICE_ID 0x1F44 // see AT25DF041A datasheet |
| 164 | #define DF_PAGE_ERASE 0x20 // erase one block of flash memory |
| 165 | #endif |
| 166 | |
| 167 | #if FLASH_MBIT == 8 |
| 168 | // settings for 1 Mbyte flash in JLv2 |
| 169 | #define DF_BLOCK_SIZE 16 // number of pages erased at same time |
| 170 | #define DF_LOG_BEGIN 32 // first 2 blocks reserved for future use |
| 171 | #define DF_LOG_LIMIT 0x0F00 // last 64k is not used for logging |
| 172 | #define DF_MEM_TOTAL 0x1000 // 4096 pages, i.e. 1 Mbyte |
| 173 | #define DF_DEVICE_ID 0x1F45 // see AT26DF081A datasheet |
| 174 | #define DF_PAGE_ERASE 0x20 // erase one block of flash memory |
| 175 | #endif |
| 176 | |
| 177 | #if FLASH_MBIT == 16 |
| 178 | // settings for 2 Mbyte flash in JLv3 |
| 179 | #define DF_BLOCK_SIZE 256 // number of pages erased at same time |
| 180 | #define DF_LOG_BEGIN 512 // first 2 blocks reserved for future use |
| 181 | #define DF_LOG_LIMIT 0x1F00 // last 64k is not used for logging |
| 182 | #define DF_MEM_TOTAL 0x2000 // 8192 pages, i.e. 2 Mbyte |
| 183 | #define DF_DEVICE_ID 0x2020 // see M25P16 datasheet |
| 184 | #define DF_PAGE_ERASE 0xD8 // erase one block of flash memory |
| 185 | #endif |
| 186 | |
| 187 | // structure of each page in the log buffer, size must be exactly 256 bytes |
| 188 | typedef struct {
|
| 189 | byte data [248]; |
| 190 | word seqnum; |
| 191 | long timestamp; |
| 192 | word crc; |
| 193 | } FlashPage; |
| 194 | |
| 195 | // structure of consecutive entries in the data area of each FlashPage |
| 196 | typedef struct {
|
| 197 | byte length; |
| 198 | byte offset; |
| 199 | byte header; |
| 200 | byte data[RF12_MAXDATA]; |
| 201 | } FlashEntry; |
| 202 | |
| 203 | static FlashPage dfBuf; // for data not yet written to flash |
| 204 | static word dfLastPage; // page number last written |
| 205 | static byte dfFill; // next byte available in buffer to store entries |
| 206 | |
| 207 | static byte df_present () {
|
| 208 | return dfLastPage != 0; |
| 209 | } |
| 210 | |
| 211 | static void df_enable () {
|
| 212 | // digitalWrite(ENABLE_PIN, 0); |
| 213 | bitClear(PORTB, 0); |
| 214 | } |
| 215 | |
| 216 | static void df_disable () {
|
| 217 | // digitalWrite(ENABLE_PIN, 1); |
| 218 | bitSet(PORTB, 0); |
| 219 | } |
| 220 | |
| 221 | static byte df_xfer (byte cmd) {
|
| 222 | SPDR = cmd; |
| 223 | while (!bitRead(SPSR, SPIF)) |
| 224 | ; |
| 225 | return SPDR; |
| 226 | } |
| 227 | |
| 228 | void df_command (byte cmd) {
|
| 229 | for (;;) {
|
| 230 | cli(); |
| 231 | df_enable(); |
| 232 | df_xfer(0x05); // Read Status Register |
| 233 | byte status = df_xfer(0); |
| 234 | df_disable(); |
| 235 | sei(); |
| 236 | // don't wait for ready bit if there is clearly no dataflash connected |
| 237 | if (status == 0xFF || (status & 1) == 0) |
| 238 | break; |
| 239 | } |
| 240 | |
| 241 | cli(); |
| 242 | df_enable(); |
| 243 | df_xfer(cmd); |
| 244 | } |
| 245 | |
| 246 | static void df_deselect () {
|
| 247 | df_disable(); |
| 248 | sei(); |
| 249 | } |
| 250 | |
| 251 | static void df_writeCmd (byte cmd) {
|
| 252 | df_command(0x06); // Write Enable |
| 253 | df_deselect(); |
| 254 | df_command(cmd); |
| 255 | } |
| 256 | |
| 257 | void df_read (word block, word off, void* buf, word len) {
|
| 258 | df_command(0x03); // Read Array (Low Frequency) |
| 259 | df_xfer(block >> 8); |
| 260 | df_xfer(block); |
| 261 | df_xfer(off); |
| 262 | for (word i = 0; i < len; ++i) |
| 263 | ((byte*) buf)[(byte) i] = df_xfer(0); |
| 264 | df_deselect(); |
| 265 | } |
| 266 | |
| 267 | void df_write (word block, const void* buf) {
|
| 268 | df_writeCmd(0x02); // Byte/Page Program |
| 269 | df_xfer(block >> 8); |
| 270 | df_xfer(block); |
| 271 | df_xfer(0); |
| 272 | for (word i = 0; i < 256; ++i) |
| 273 | df_xfer(((const byte*) buf)[(byte) i]); |
| 274 | df_deselect(); |
| 275 | } |
| 276 | |
| 277 | // wait for current command to complete |
| 278 | void df_flush () {
|
| 279 | df_read(0, 0, 0, 0); |
| 280 | } |
| 281 | |
| 282 | static void df_wipe () {
|
| 283 | Serial.println("DF W");
|
| 284 | |
| 285 | df_writeCmd(0xC7); // Chip Erase |
| 286 | df_deselect(); |
| 287 | df_flush(); |
| 288 | } |
| 289 | |
| 290 | static void df_erase (word block) {
|
| 291 | Serial.print("DF E ");
|
| 292 | Serial.println(block); |
| 293 | |
| 294 | df_writeCmd(DF_PAGE_ERASE); // Block Erase |
| 295 | df_xfer(block >> 8); |
| 296 | df_xfer(block); |
| 297 | df_xfer(0); |
| 298 | df_deselect(); |
| 299 | df_flush(); |
| 300 | } |
| 301 | |
| 302 | static word df_wrap (word page) {
|
| 303 | return page < DF_LOG_LIMIT ? page : DF_LOG_BEGIN; |
| 304 | } |
| 305 | |
| 306 | static void df_saveBuf () {
|
| 307 | if (dfFill == 0) |
| 308 | return; |
| 309 | |
| 310 | dfLastPage = df_wrap(dfLastPage + 1); |
| 311 | if (dfLastPage == DF_LOG_BEGIN) |
| 312 | ++dfBuf.seqnum; // bump to next seqnum when wrapping |
| 313 | |
| 314 | // set remainder of buffer data to 0xFF and calculate crc over entire buffer |
| 315 | dfBuf.crc = ~0; |
| 316 | for (byte i = 0; i < sizeof dfBuf - 2; ++i) {
|
| 317 | if (dfFill <= i && i < sizeof dfBuf.data) |
| 318 | dfBuf.data[i] = 0xFF; |
| 319 | dfBuf.crc = _crc16_update(dfBuf.crc, dfBuf.data[i]); |
| 320 | } |
| 321 | |
| 322 | df_write(dfLastPage, &dfBuf); |
| 323 | dfFill = 0; |
| 324 | |
| 325 | // wait for write to finish before reporting page, seqnum, and time stamp |
| 326 | df_flush(); |
| 327 | Serial.print("DF S ");
|
| 328 | Serial.print(dfLastPage); |
| 329 | Serial.print(' ');
|
| 330 | Serial.print(dfBuf.seqnum); |
| 331 | Serial.print(' ');
|
| 332 | Serial.println(dfBuf.timestamp); |
| 333 | |
| 334 | // erase next block if we just saved data into a fresh block |
| 335 | // at this point in time dfBuf is empty, so a lengthy erase cycle is ok |
| 336 | if (dfLastPage % DF_BLOCK_SIZE == 0) |
| 337 | df_erase(df_wrap(dfLastPage + DF_BLOCK_SIZE)); |
| 338 | } |
| 339 | |
| 340 | static void df_append (const void* buf, byte len) {
|
| 341 | //FIXME the current logic can't append incoming packets during a save! |
| 342 | |
| 343 | // fill in page time stamp when appending to a fresh page |
| 344 | if (dfFill == 0) |
| 345 | dfBuf.timestamp = now(); |
| 346 | |
| 347 | long offset = now() - dfBuf.timestamp; |
| 348 | if (offset >= 255 || dfFill + 1 + len > sizeof dfBuf.data) {
|
| 349 | df_saveBuf(); |
| 350 | |
| 351 | dfBuf.timestamp = now(); |
| 352 | offset = 0; |
| 353 | } |
| 354 | |
| 355 | // append new entry to flash buffer |
| 356 | dfBuf.data[dfFill++] = offset; |
| 357 | memcpy(dfBuf.data + dfFill, buf, len); |
| 358 | dfFill += len; |
| 359 | } |
| 360 | |
| 361 | // go through entire log buffer to figure out which page was last saved |
| 362 | static void scanForLastSave () {
|
| 363 | dfBuf.seqnum = 0; |
| 364 | dfLastPage = DF_LOG_LIMIT - 1; |
| 365 | // look for last page before an empty page |
| 366 | for (word page = DF_LOG_BEGIN; page < DF_LOG_LIMIT; ++page) {
|
| 367 | word currseq; |
| 368 | df_read(page, sizeof dfBuf.data, &currseq, sizeof currseq); |
| 369 | if (currseq != 0xFFFF) {
|
| 370 | dfLastPage = page; |
| 371 | dfBuf.seqnum = currseq + 1; |
| 372 | } else if (dfLastPage == page - 1) |
| 373 | break; // careful with empty-filled-empty case, i.e. after wrap |
| 374 | } |
| 375 | } |
| 376 | |
| 377 | static void df_initialize () {
|
| 378 | // assumes SPI has already been initialized for the RFM12B |
| 379 | df_disable(); |
| 380 | pinMode(DF_ENABLE_PIN, OUTPUT); |
| 381 | df_command(0x9F); // Read Manufacturer and Device ID |
| 382 | word info = df_xfer(0) << 8; |
| 383 | info |= df_xfer(0); |
| 384 | df_deselect(); |
| 385 | |
| 386 | if (info == DF_DEVICE_ID) {
|
| 387 | df_writeCmd(0x01); // Write Status Register ... |
| 388 | df_xfer(0); // ... Global Unprotect |
| 389 | df_deselect(); |
| 390 | |
| 391 | scanForLastSave(); |
| 392 | |
| 393 | Serial.print("DF I ");
|
| 394 | Serial.print(dfLastPage); |
| 395 | Serial.print(' ');
|
| 396 | Serial.println(dfBuf.seqnum); |
| 397 | |
| 398 | // df_wipe(); |
| 399 | df_saveBuf(); //XXX |
| 400 | } |
| 401 | } |
| 402 | |
| 403 | static void discardInput () {
|
| 404 | while (Serial.read() >= 0) |
| 405 | ; |
| 406 | } |
| 407 | |
| 408 | static void df_dump () {
|
| 409 | struct { word seqnum; long timestamp; word crc; } curr;
|
| 410 | discardInput(); |
| 411 | for (word page = DF_LOG_BEGIN; page < DF_LOG_LIMIT; ++page) {
|
| 412 | if (Serial.read() >= 0) |
| 413 | break; |
| 414 | // read marker from page in flash |
| 415 | df_read(page, sizeof dfBuf.data, &curr, sizeof curr); |
| 416 | if (curr.seqnum == 0xFFFF) |
| 417 | continue; // page never written to |
| 418 | Serial.print(" df# ");
|
| 419 | Serial.print(page); |
| 420 | Serial.print(" : ");
|
| 421 | Serial.print(curr.seqnum); |
| 422 | Serial.print(' ');
|
| 423 | Serial.print(curr.timestamp); |
| 424 | Serial.print(' ');
|
| 425 | Serial.println(curr.crc); |
| 426 | } |
| 427 | } |
| 428 | |
| 429 | static word scanForMarker (word seqnum, long asof) {
|
| 430 | word lastPage = 0; |
| 431 | struct { word seqnum; long timestamp; } last, curr;
|
| 432 | last.seqnum = 0xFFFF; |
| 433 | // go through all the pages in log area of flash |
| 434 | for (word page = DF_LOG_BEGIN; page < DF_LOG_LIMIT; ++page) {
|
| 435 | // read seqnum and timestamp from page in flash |
| 436 | df_read(page, sizeof dfBuf.data, &curr, sizeof curr); |
| 437 | if (curr.seqnum == 0xFFFF) |
| 438 | continue; // page never written to |
| 439 | if (curr.seqnum >= seqnum && curr.seqnum < last.seqnum) {
|
| 440 | last = curr; |
| 441 | lastPage = page; |
| 442 | } |
| 443 | if (curr.seqnum == last.seqnum && curr.timestamp <= asof) |
| 444 | lastPage = page; |
| 445 | } |
| 446 | return lastPage; |
| 447 | } |
| 448 | |
| 449 | static void df_replay (word seqnum, long asof) {
|
| 450 | word page = scanForMarker(seqnum, asof); |
| 451 | Serial.print("r: page ");
|
| 452 | Serial.print(page); |
| 453 | Serial.print(' ');
|
| 454 | Serial.println(dfLastPage); |
| 455 | discardInput(); |
| 456 | word savedSeqnum = dfBuf.seqnum; |
| 457 | while (page != dfLastPage) {
|
| 458 | if (Serial.read() >= 0) |
| 459 | break; |
| 460 | page = df_wrap(page + 1); |
| 461 | df_read(page, 0, &dfBuf, sizeof dfBuf); // overwrites ram buffer! |
| 462 | if (dfBuf.seqnum == 0xFFFF) |
| 463 | continue; // page never written to |
| 464 | // skip and report bad pages |
| 465 | word crc = ~0; |
| 466 | for (word i = 0; i < sizeof dfBuf; ++i) |
| 467 | crc = _crc16_update(crc, dfBuf.data[i]); |
| 468 | if (crc != 0) {
|
| 469 | Serial.print("DF C? ");
|
| 470 | Serial.print(page); |
| 471 | Serial.print(' ');
|
| 472 | Serial.println(crc); |
| 473 | continue; |
| 474 | } |
| 475 | // report each entry as "R seqnum time <data...>" |
| 476 | byte i = 0; |
| 477 | while (i < sizeof dfBuf.data && dfBuf.data[i] < 255) {
|
| 478 | if (Serial.available()) |
| 479 | break; |
| 480 | Serial.print("R ");
|
| 481 | Serial.print(dfBuf.seqnum); |
| 482 | Serial.print(' ');
|
| 483 | Serial.print(dfBuf.timestamp + dfBuf.data[i++]); |
| 484 | Serial.print(' ');
|
| 485 | Serial.print((int) dfBuf.data[i++]); |
| 486 | byte n = dfBuf.data[i++]; |
| 487 | while (n-- > 0) {
|
| 488 | Serial.print(' ');
|
| 489 | Serial.print((int) dfBuf.data[i++]); |
| 490 | } |
| 491 | Serial.println(); |
| 492 | } |
| 493 | // at end of each page, report a "DF R" marker, to allow re-starting |
| 494 | Serial.print("DF R ");
|
| 495 | Serial.print(page); |
| 496 | Serial.print(' ');
|
| 497 | Serial.print(dfBuf.seqnum); |
| 498 | Serial.print(' ');
|
| 499 | Serial.println(dfBuf.timestamp); |
| 500 | } |
| 501 | dfFill = 0; // ram buffer is no longer valid |
| 502 | dfBuf.seqnum = savedSeqnum + 1; // so next replay will start at a new value |
| 503 | Serial.print("DF E ");
|
| 504 | Serial.print(dfLastPage); |
| 505 | Serial.print(' ');
|
| 506 | Serial.print(dfBuf.seqnum); |
| 507 | Serial.print(' ');
|
| 508 | Serial.println(millis()); |
| 509 | } |
| 510 | |
| 511 | #else // DATAFLASH |
| 512 | |
| 513 | #define df_present() 0 |
| 514 | #define df_initialize() |
| 515 | #define df_dump() |
| 516 | #define df_replay(x,y) |
| 517 | #define df_erase(x) |
| 518 | |
| 519 | #endif |
| 520 | |
| 521 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
| 522 | |
| 523 | char helpText1[] PROGMEM = |
| 524 | "\n" |
| 525 | "Available commands:" "\n" |
| 526 | " <nn> i - set node ID (standard node ids are 1..26)" "\n" |
| 527 | " (or enter an uppercase 'A'..'Z' to set id)" "\n" |
| 528 | " <n> b - set MHz band (4 = 433, 8 = 868, 9 = 915)" "\n" |
| 529 | " <nnn> g - set network group (RFM12 only allows 212, 0 = any)" "\n" |
| 530 | " <n> c - set collect mode (advanced, normally 0)" "\n" |
| 531 | " t - broadcast max-size test packet, with ack" "\n" |
| 532 | " ...,<nn> a - send data packet to node <nn>, with ack" "\n" |
| 533 | " ...,<nn> s - send data packet to node <nn>, no ack" "\n" |
| 534 | " <n> l - turn activity LED on PB1 on or off" "\n" |
| 535 | " <n> q - set quiet mode (1 = don't report bad packets)" "\n" |
| 536 | "Remote control commands:" "\n" |
| 537 | " <hchi>,<hclo>,<addr>,<cmd> f - FS20 command (868 MHz)" "\n" |
| 538 | " <addr>,<dev>,<on> k - KAKU command (433 MHz)" "\n" |
| 539 | ; |
| 540 | char helpText2[] PROGMEM = |
| 541 | "Flash storage (JeeLink only):" "\n" |
| 542 | " d - dump all log markers" "\n" |
| 543 | " <sh>,<sl>,<t3>,<t2>,<t1>,<t0> r - replay from specified marker" "\n" |
| 544 | " 123,<bhi>,<blo> e - erase 4K block" "\n" |
| 545 | " 12,34 w - wipe entire flash memory" "\n" |
| 546 | ; |
| 547 | |
| 548 | static void showString (PGM_P s) {
|
| 549 | for (;;) {
|
| 550 | char c = pgm_read_byte(s++); |
| 551 | if (c == 0) |
| 552 | break; |
| 553 | if (c == '\n') |
| 554 | Serial.print('\r');
|
| 555 | Serial.print(c); |
| 556 | } |
| 557 | } |
| 558 | |
| 559 | static void showHelp () {
|
| 560 | showString(helpText1); |
| 561 | if (df_present()) |
| 562 | showString(helpText2); |
| 563 | Serial.println("Current configuration:");
|
| 564 | rf12_config(); |
| 565 | } |
| 566 | |
| 567 | static void handleInput (char c) {
|
| 568 | if ('0' <= c && c <= '9')
|
| 569 | value = 10 * value + c - '0'; |
| 570 | else if (c == ',') {
|
| 571 | if (top < sizeof stack) |
| 572 | stack[top++] = value; |
| 573 | value = 0; |
| 574 | } else if ('a' <= c && c <='z') {
|
| 575 | Serial.print("> ");
|
| 576 | Serial.print((int) value); |
| 577 | Serial.println(c); |
| 578 | switch (c) {
|
| 579 | default: |
| 580 | showHelp(); |
| 581 | break; |
| 582 | case 'i': // set node id |
| 583 | config.nodeId = (config.nodeId & 0xE0) + (value & 0x1F); |
| 584 | saveConfig(); |
| 585 | break; |
| 586 | case 'b': // set band: 4 = 433, 8 = 868, 9 = 915 |
| 587 | value = value == 8 ? RF12_868MHZ : |
| 588 | value == 9 ? RF12_915MHZ : RF12_433MHZ; |
| 589 | config.nodeId = (value << 6) + (config.nodeId & 0x3F); |
| 590 | saveConfig(); |
| 591 | break; |
| 592 | case 'g': // set network group |
| 593 | config.group = value; |
| 594 | saveConfig(); |
| 595 | break; |
| 596 | case 'c': // set collect mode (off = 0, on = 1) |
| 597 | if (value) |
| 598 | config.nodeId |= COLLECT; |
| 599 | else |
| 600 | config.nodeId &= ~COLLECT; |
| 601 | saveConfig(); |
| 602 | break; |
| 603 | case 't': // broadcast a maximum size test packet, request an ack |
| 604 | cmd = 'a'; |
| 605 | sendLen = RF12_MAXDATA; |
| 606 | dest = 0; |
| 607 | for (byte i = 0; i < RF12_MAXDATA; ++i) |
| 608 | testbuf[i] = i + testCounter; |
| 609 | Serial.print("test ");
|
| 610 | Serial.println((int) testCounter); // first byte in test buffer |
| 611 | ++testCounter; |
| 612 | break; |
| 613 | case 'a': // send packet to node ID N, request an ack |
| 614 | case 's': // send packet to node ID N, no ack |
| 615 | cmd = c; |
| 616 | sendLen = top; |
| 617 | dest = value; |
| 618 | memcpy(testbuf, stack, top); |
| 619 | break; |
| 620 | case 'l': // turn activity LED on or off |
| 621 | activityLed(value); |
| 622 | break; |
| 623 | case 'f': // send FS20 command: <hchi>,<hclo>,<addr>,<cmd>f |
| 624 | rf12_initialize(0, RF12_868MHZ); |
| 625 | activityLed(1); |
| 626 | fs20cmd(256 * stack[0] + stack[1], stack[2], value); |
| 627 | activityLed(0); |
| 628 | rf12_config(); // restore normal packet listening mode |
| 629 | break; |
| 630 | case 'k': // send KAKU command: <addr>,<dev>,<on>k |
| 631 | rf12_initialize(0, RF12_433MHZ); |
| 632 | activityLed(1); |
| 633 | kakuSend(stack[0], stack[1], value); |
| 634 | activityLed(0); |
| 635 | rf12_config(); // restore normal packet listening mode |
| 636 | break; |
| 637 | case 'd': // dump all log markers |
| 638 | if (df_present()) |
| 639 | df_dump(); |
| 640 | break; |
| 641 | case 'r': // replay from specified seqnum/time marker |
| 642 | if (df_present()) {
|
| 643 | word seqnum = (stack[0] << 8) || stack[1]; |
| 644 | long asof = (stack[2] << 8) || stack[3]; |
| 645 | asof = (asof << 16) | ((stack[4] << 8) || value); |
| 646 | df_replay(seqnum, asof); |
| 647 | } |
| 648 | break; |
| 649 | case 'e': // erase specified 4Kb block |
| 650 | if (df_present() && stack[0] == 123) {
|
| 651 | word block = (stack[1] << 8) | value; |
| 652 | df_erase(block); |
| 653 | } |
| 654 | break; |
| 655 | case 'w': // wipe entire flash memory |
| 656 | if (df_present() && stack[0] == 12 && value == 34) {
|
| 657 | df_wipe(); |
| 658 | Serial.println("erased");
|
| 659 | } |
| 660 | break; |
| 661 | case 'q': // turn quiet mode on or off (don't report bad packets) |
| 662 | quiet = value; |
| 663 | break; |
| 664 | } |
| 665 | value = top = 0; |
| 666 | memset(stack, 0, sizeof stack); |
| 667 | } else if ('A' <= c && c <= 'Z') {
|
| 668 | config.nodeId = (config.nodeId & 0xE0) + (c & 0x1F); |
| 669 | saveConfig(); |
| 670 | } else if (c > ' ') |
| 671 | showHelp(); |
| 672 | } |
| 673 | |
| 674 | void setup() {
|
| 675 | Serial.begin(57600); |
| 676 | Serial.print("\n[RF12demo.8]");
|
| 677 | |
| 678 | if (rf12_config()) {
|
| 679 | config.nodeId = eeprom_read_byte(RF12_EEPROM_ADDR); |
| 680 | config.group = eeprom_read_byte(RF12_EEPROM_ADDR + 1); |
| 681 | } else {
|
| 682 | config.nodeId = 0x41; // node A1 @ 433 MHz |
| 683 | config.group = 0xD4; |
| 684 | saveConfig(); |
| 685 | } |
| 686 | |
| 687 | df_initialize(); |
| 688 | |
| 689 | showHelp(); |
| 690 | } |
| 691 | |
| 692 | void loop() {
|
| 693 | if (Serial.available()) |
| 694 | handleInput(Serial.read()); |
| 695 | |
| 696 | if (rf12_recvDone()) {
|
| 697 | byte n = rf12_len; |
| 698 | if (rf12_crc == 0) {
|
| 699 | Serial.print("OK");
|
| 700 | } else {
|
| 701 | if (quiet) |
| 702 | return; |
| 703 | Serial.print(" ?");
|
| 704 | if (n > 20) // print at most 20 bytes if crc is wrong |
| 705 | n = 20; |
| 706 | } |
| 707 | if (config.group == 0) {
|
| 708 | Serial.print("G ");
|
| 709 | Serial.print((int) rf12_grp); |
| 710 | } |
| 711 | Serial.print(' ');
|
| 712 | Serial.print((int) rf12_hdr); |
| 713 | for (byte i = 0; i < n; ++i) {
|
| 714 | Serial.print(' ');
|
| 715 | Serial.print((int) rf12_data[i]); |
| 716 | } |
| 717 | Serial.println(); |
| 718 | |
| 719 | if (rf12_crc == 0) {
|
| 720 | activityLed(1); |
| 721 | |
| 722 | if (df_present()) |
| 723 | df_append((const char*) rf12_data - 2, rf12_len + 2); |
| 724 | |
| 725 | if (RF12_WANTS_ACK && (config.nodeId & COLLECT) == 0) {
|
| 726 | Serial.println(" -> ack");
|
| 727 | rf12_sendStart(RF12_ACK_REPLY, 0, 0); |
| 728 | } |
| 729 | |
| 730 | activityLed(0); |
| 731 | } |
| 732 | } |
| 733 | |
| 734 | if (cmd && rf12_canSend()) {
|
| 735 | activityLed(1); |
| 736 | |
| 737 | Serial.print(" -> ");
|
| 738 | Serial.print((int) sendLen); |
| 739 | Serial.println(" b");
|
| 740 | byte header = cmd == 'a' ? RF12_HDR_ACK : 0; |
| 741 | if (dest) |
| 742 | header |= RF12_HDR_DST | dest; |
| 743 | rf12_sendStart(header, testbuf, sendLen); |
| 744 | cmd = 0; |
| 745 | |
| 746 | activityLed(0); |
| 747 | } |
| 748 | } |