Редизайн на основе текущей ветки мейнстрима + новые устройства.
[rtl-433.git] / src / devices / emontx.c
diff --git a/src/devices/emontx.c b/src/devices/emontx.c
new file mode 100644 (file)
index 0000000..cf04c3b
--- /dev/null
@@ -0,0 +1,150 @@
+/* OpenEnergyMonitor.org emonTx sensor protocol
+ *
+ * Copyright © 2016 Tommy Vestermark
+ * Copyright © 2016 David Woodhouse
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+#include "rtl_433.h"
+#include "util.h"
+
+// We don't really *use* this because there's no endianness support
+// for just using le16_to_cpu(pkt.ct1) etc. A task for another day...
+struct emontx {
+       uint8_t syn, group, node, len;
+       uint16_t ct1, ct2, ct3, ct4, Vrms, temp[6];
+       uint32_t pulse;
+       uint16_t crc;
+       uint8_t postamble;
+} __attribute__((packed));
+
+// This is the JeeLibs RF12 packet format as described at
+// http://jeelabs.org/2011/06/09/rf12-packet-format-and-design/
+//
+// The RFM69 chip misses out the zero bit at the end of the
+// 0xAA 0xAA 0xAA preamble; the receivers only use it to set
+// up the bit timing, and they look for the 0x2D at the start
+// of the packet. So we'll do the same — except since we're
+// specifically looking for emonTx packets, we can require a
+// little bit more. We look for a group of 0xD2, and we
+// expect the CDA bits in the header to all be zero:
+static unsigned char preamble[3] = { 0xaa, 0xaa, 0xaa };
+static unsigned char pkt_hdr_inverted[3] = { 0xd2, 0x2d, 0xc0 };
+static unsigned char pkt_hdr[3] = { 0x2d, 0xd2, 0x00 };
+
+static int emontx_callback(bitbuffer_t *bitbuffer) {
+       bitrow_t *bb = bitbuffer->bb;
+       unsigned bitpos = 0;
+       unsigned bits = bitbuffer->bits_per_row[0];
+       int events = 0;
+
+       // Search for only 22 bits to cope with inverted frames and
+       // the missing final preamble bit with RFM69 transmissions.
+       while ((bitpos = bitbuffer_search(bitbuffer, 0, bitpos,
+                                         preamble, 22)) < bitbuffer->bits_per_row[0]) {
+               int inverted = 0;
+               unsigned pkt_pos;
+               uint16_t crc;
+               data_t *data;
+               char time_str[LOCAL_TIME_BUFLEN];
+               union {
+                       struct emontx p;
+                       uint8_t b[sizeof(struct emontx)];
+               } pkt;
+               uint16_t words[14];
+               double vrms;
+               unsigned i;
+
+               bitpos += 22;
+
+               // Eat any additional 101010 sequences (which might be attributed
+               // to noise at the start of the packet which coincidentally matches).
+               while (bitbuffer_search(bitbuffer, 0, bitpos, preamble, 2) == bitpos)
+                       bitpos += 2;
+
+               // Account for RFM69 bug which drops a zero bit at the end of the
+               // preamble before the 0x2d SYN byte. And for inversion.
+               bitpos--;
+
+               // Check for non-inverted packet header...
+               pkt_pos = bitbuffer_search(bitbuffer, 0, bitpos,
+                                          pkt_hdr, 11);
+
+               // And for inverted, if it's not found close enough...
+               if (pkt_pos > bitpos + 5) {
+                       pkt_pos = bitbuffer_search(bitbuffer, 0, bitpos,
+                                                  pkt_hdr_inverted, 11);
+                       if (pkt_pos > bitpos + 5)
+                               continue;
+                       inverted = 1;
+               }
+
+               // Need enough data for a full packet (including postamble)
+               if (pkt_pos + sizeof(pkt)*8 > bitbuffer->bits_per_row[0])
+                       break;
+
+               // Extract the group even though we matched on it; the CRC
+               // covers it too. And might as well have the 0x2d too for
+               // alignment.
+               bitbuffer_extract_bytes(bitbuffer, 0, pkt_pos,
+                                       (uint8_t *)&pkt, sizeof(pkt) * 8);
+               if (inverted) {
+                       for (i=0; i<sizeof(pkt); i++)
+                               pkt.b[i] ^= 0xff;
+               }
+               if (pkt.p.len != 0x1a || pkt.p.postamble != 0xaa)
+                       continue;
+               crc = crc16((uint8_t *)&pkt.p.group, 0x1d, 0xa001, 0xffff);
+
+               // Ick. If we could just do le16_to_cpu(pkt.p.ct1) we wouldn't need this.
+               for (i=0; i<14; i++)
+                       words[i] = pkt.b[4 + (i * 2)] | (pkt.b[5 + i * 2] << 8);
+               if (crc != words[13])
+                       continue;
+
+               vrms = (double)words[4] / 100.0;
+
+               local_time_str(0, time_str);
+               data = data_make("time", "", DATA_STRING, time_str,
+                                "model", "", DATA_STRING, "emonTx",
+                                "node", "", DATA_FORMAT, "%02x", DATA_INT, pkt.p.node & 0x1f,
+                                "ct1", "", DATA_FORMAT, "%d", DATA_INT, (int16_t)words[0],
+                                "ct2", "", DATA_FORMAT, "%d", DATA_INT, (int16_t)words[1],
+                                "ct3", "", DATA_FORMAT, "%d", DATA_INT, (int16_t)words[2],
+                                "ct4", "", DATA_FORMAT, "%d", DATA_INT, (int16_t)words[3],
+                                "Vrms/batt", "", DATA_FORMAT, "%.2f", DATA_DOUBLE, vrms,
+                                "pulse", "", DATA_FORMAT, "%u", DATA_INT, words[11] | ((uint32_t)words[12] << 16),
+                                // Slightly horrid... a value of 300.0°C means 'no reading'. So omit them completely.
+                                words[5] == 3000 ? NULL : "temp1_C", "", DATA_FORMAT, "%.1f", DATA_DOUBLE, (double)words[5] / 10.0,
+                                words[6] == 3000 ? NULL : "temp2_C", "", DATA_FORMAT, "%.1f", DATA_DOUBLE, (double)words[6] / 10.0,
+                                words[7] == 3000 ? NULL : "temp3_C", "", DATA_FORMAT, "%.1f", DATA_DOUBLE, (double)words[7] / 10.0,
+                                words[8] == 3000 ? NULL : "temp4_C", "", DATA_FORMAT, "%.1f", DATA_DOUBLE, (double)words[8] / 10.0,
+                                words[9] == 3000 ? NULL : "temp5_C", "", DATA_FORMAT, "%.1f", DATA_DOUBLE, (double)words[9] / 10.0,
+                                words[10] == 3000 ? NULL : "temp6_C", "", DATA_FORMAT, "%.1f", DATA_DOUBLE, (double)words[10] / 10.0,
+                                NULL);
+               data_acquired_handler(data);
+               events++;
+       }
+       return events;
+}
+
+static char *output_fields[] = {
+       "time", "model", "node", "ct1", "ct2", "ct3", "ct4", "Vrms/batt",
+       "temp1_C", "temp2_C", "temp3_C", "temp4_C", "temp5_C", "temp6_C",
+       "pulse", NULL
+};
+
+r_device emontx = {
+       .name           = "emonTx OpenEnergyMonitor",
+       .modulation     = FSK_PULSE_PCM,
+       .short_limit    = 2000000.0 / (49230 + 49261), // 49261kHz for RFM69, 49230kHz for RFM12B
+       .long_limit     = 2000000.0 / (49230 + 49261),
+       .reset_limit    = 1200, // 600 zeros...
+       .json_callback  = &emontx_callback,
+       .disabled       = 0,
+       .demod_arg      = 0,
+       .fields         = output_fields,
+};