|
| 1 | +/** @file |
| 2 | + RFM69 decoder as used on LowPowerLabs Moteino boards |
| 3 | +
|
| 4 | + Copyright (C) 2025 Ian Cockett <cockettian@gmail.com> |
| 5 | +
|
| 6 | + This program is free software; you can redistribute it and/or modify |
| 7 | + it under the terms of the GNU General Public License as published by |
| 8 | + the Free Software Foundation; either version 2 of the License, or |
| 9 | + (at your option) any later version. |
| 10 | +*/ |
| 11 | + |
| 12 | +#include "decoder.h" |
| 13 | + |
| 14 | +/** |
| 15 | +
|
| 16 | +Generic decoder for RFM69 radio modules as used on LowPowerLab.com Moteino boards. |
| 17 | +
|
| 18 | + rtl_433 -s 1000k |
| 19 | +
|
| 20 | +Test data captured with sample sketch https://github.com/LowPowerLab/RFM69/blob/master/Examples/Node/Node.ino |
| 21 | +
|
| 22 | +Encryption must be disabled in the sketch (comment out #define ENCRYPTKEY) |
| 23 | +Data captures from 433MHz RFM69HW_HCW board, but 868MHz models should be similar |
| 24 | +
|
| 25 | +Protocol description:- |
| 26 | +
|
| 27 | +- Preamble aaaaaa |
| 28 | +- Sync word 2d64 |
| 29 | +- Header byte 1 - Length Byte |
| 30 | +- Header byte 2 - Dest Address |
| 31 | +- Header byte 3 - Src Address |
| 32 | +- Header byte 4 - Control byte (not sure what this does) |
| 33 | +- n bytes variable length message. |
| 34 | +- CRC16 checksum |
| 35 | +
|
| 36 | +*/ |
| 37 | + |
| 38 | +#define LENGTH_POS 5 |
| 39 | +#define NODE_ID_POS 7 |
| 40 | +#define DATA_START_POS 9 |
| 41 | + |
| 42 | +#define HEADER_LENGTH 6 |
| 43 | +#define MAX_LENGTH 65 |
| 44 | +#define BUF_LENGTH 72 |
| 45 | + |
| 46 | +static int rfm69_fsk_callback(r_device *decoder, bitbuffer_t *bitbuffer) |
| 47 | +{ |
| 48 | + data_t *data; |
| 49 | + |
| 50 | + uint8_t search = 0x2d; // sync byte to scan for |
| 51 | + |
| 52 | + uint8_t message[BUF_LENGTH]; // max size of header + payload + terminator |
| 53 | + uint8_t payload[MAX_LENGTH]; // max size of payload + terminator |
| 54 | + |
| 55 | + unsigned posn = bitbuffer_search(bitbuffer, 0, 0, &search, 8); |
| 56 | + |
| 57 | + if ((posn < 24) || (posn > 28)) |
| 58 | + return 0; // Can't find bit position of sync word |
| 59 | + |
| 60 | + bitbuffer_extract_bytes(bitbuffer, 0, posn - 24, (uint8_t *)&message, (MAX_LENGTH) * 8); // Extract out full into our aligned buffer. Include 3x8 bits of sync word before the preamble |
| 61 | + |
| 62 | + uint8_t payload_len = message[LENGTH_POS]; |
| 63 | + |
| 64 | + if (payload_len > MAX_LENGTH) |
| 65 | + return 0; // message junk |
| 66 | + |
| 67 | + bitbuffer_extract_bytes(bitbuffer, 0, posn + 16, (uint8_t *)&payload, (payload_len + 1) * 8); // we need to include length byte in CRC calc |
| 68 | + |
| 69 | + // found the polynomial values in an old Semtech application note. |
| 70 | + uint16_t crc = ~crc16(payload, (payload_len + 1), 0x1021, 0x1d0f) & 0xffff; |
| 71 | + |
| 72 | + if (((crc >> 8) != message[HEADER_LENGTH + payload_len + 0]) || |
| 73 | + ((crc & 0x00ff) != message[HEADER_LENGTH + payload_len + 1])) { |
| 74 | + fprintf(stderr, "CRC NOT OK!\n"); |
| 75 | + return 0; |
| 76 | + } |
| 77 | + |
| 78 | + if (message[NODE_ID_POS] == 0x02) { |
| 79 | + message[HEADER_LENGTH + payload_len] = 0x00; // remove the checksum which is still at the end of the message buffer |
| 80 | + |
| 81 | + char strNode[2]; |
| 82 | + char strGateway[2]; |
| 83 | + char strMessage[32]; |
| 84 | + |
| 85 | + sprintf(strGateway, "%d", (message[HEADER_LENGTH])); |
| 86 | + sprintf(strNode, "%d", (message[NODE_ID_POS])); |
| 87 | + sprintf(strMessage, "%.30s", &message[DATA_START_POS]); |
| 88 | + |
| 89 | + /* clang-format off */ |
| 90 | + data = data_make( |
| 91 | + "model", "Model", DATA_STRING, "Moteino-RFM69", |
| 92 | + "id", "Node Id ", DATA_STRING, strNode, |
| 93 | + "gateway_id", "Gateway Id", DATA_STRING, strGateway, |
| 94 | + "msg", "Message", DATA_STRING, strMessage, |
| 95 | + "mic", "Integrity", DATA_STRING, "CRC", |
| 96 | + NULL); |
| 97 | + /* clang-format on */ |
| 98 | + |
| 99 | + decoder_output_data(decoder, data); |
| 100 | + |
| 101 | + return 1; |
| 102 | + } |
| 103 | + |
| 104 | + if (message[NODE_ID_POS] == 0xff) // your node id |
| 105 | + { |
| 106 | + // Add your own stuff |
| 107 | + } |
| 108 | + |
| 109 | + return 0; |
| 110 | +} |
| 111 | + |
| 112 | +static char const *const output_fields[] = { |
| 113 | + "model", |
| 114 | + "gateway_id", |
| 115 | + "id", |
| 116 | + "msg", |
| 117 | + "mic", |
| 118 | + NULL, |
| 119 | +}; |
| 120 | + |
| 121 | +r_device const rfm69_lowpowerlab_moteino = { |
| 122 | + .name = "RFM69 LowPowerLab Moteino board (-s 1000k)", |
| 123 | + .modulation = FSK_PULSE_PCM, |
| 124 | + .short_width = 18, |
| 125 | + .long_width = 18, |
| 126 | + .reset_limit = 400, |
| 127 | + .decode_fn = &rfm69_fsk_callback, |
| 128 | + .fields = output_fields, |
| 129 | + |
| 130 | +}; |
0 commit comments