
- Change hard coded strings are "const" so any variable pointing to them must also have the const specifier to avoid a warning (from -Wdiscarded-qualifiers) - Change local variables manipulating constant strings must also have the const qualifier - Change hostport_param, fix remaining hostport_param args Co-authored-by: Christian W. Zuckschwerdt <christian@zuckschwerdt.org>
168 lines
6.6 KiB
C
168 lines
6.6 KiB
C
/** @file
|
|
OpenEnergyMonitor.org emonTx sensor protocol.
|
|
|
|
Copyright (C) 2016 Tommy Vestermark
|
|
Copyright (C) 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 "decoder.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...
|
|
#pragma pack(push, 1)
|
|
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;
|
|
};
|
|
#pragma pack(pop)
|
|
|
|
/** @fn int emontx_callback(r_device *decoder, bitbuffer_t *bitbuffer)
|
|
OpenEnergyMonitor.org emonTx sensor protocol.
|
|
|
|
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(r_device *decoder, bitbuffer_t *bitbuffer)
|
|
{
|
|
unsigned bitpos = 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;
|
|
union {
|
|
struct emontx p;
|
|
uint8_t b[sizeof(struct emontx)];
|
|
} pkt;
|
|
uint16_t words[14];
|
|
float 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; // DECODE_ABORT_EARLY
|
|
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; // DECODE_ABORT_EARLY
|
|
crc = crc16lsb((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; // DECODE_FAIL_MIC
|
|
|
|
vrms = (float)words[4] / 100.0;
|
|
|
|
/* clang-format off */
|
|
data = data_make(
|
|
"model", "", DATA_STRING, "emonTx-Energy",
|
|
"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],
|
|
"batt_Vrms", "", 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.
|
|
"temp1_C", "", DATA_COND, words[5] != 3000, DATA_FORMAT, "%.1f", DATA_DOUBLE, words[5] * 0.1f,
|
|
"temp2_C", "", DATA_COND, words[6] != 3000, DATA_FORMAT, "%.1f", DATA_DOUBLE, words[6] * 0.1f,
|
|
"temp3_C", "", DATA_COND, words[7] != 3000, DATA_FORMAT, "%.1f", DATA_DOUBLE, words[7] * 0.1f,
|
|
"temp4_C", "", DATA_COND, words[8] != 3000, DATA_FORMAT, "%.1f", DATA_DOUBLE, words[8] * 0.1f,
|
|
"temp5_C", "", DATA_COND, words[9] != 3000, DATA_FORMAT, "%.1f", DATA_DOUBLE, words[9] * 0.1f,
|
|
"temp6_C", "", DATA_COND, words[10] != 3000, DATA_FORMAT, "%.1f", DATA_DOUBLE, words[10] * 0.1f,
|
|
"mic", "Integrity", DATA_STRING, "CRC",
|
|
NULL);
|
|
/* clang-format on */
|
|
decoder_output_data(decoder, data);
|
|
events++;
|
|
}
|
|
return events;
|
|
}
|
|
|
|
static char const *output_fields[] = {
|
|
"model",
|
|
"node",
|
|
"ct1",
|
|
"ct2",
|
|
"ct3",
|
|
"ct4",
|
|
"batt_Vrms",
|
|
"temp1_C",
|
|
"temp2_C",
|
|
"temp3_C",
|
|
"temp4_C",
|
|
"temp5_C",
|
|
"temp6_C",
|
|
"pulse",
|
|
"mic",
|
|
NULL,
|
|
};
|
|
|
|
r_device const emontx = {
|
|
.name = "emonTx OpenEnergyMonitor",
|
|
.modulation = FSK_PULSE_PCM,
|
|
.short_width = 2000000.0f / (49230 + 49261), // 49261kHz for RFM69, 49230kHz for RFM12B
|
|
.long_width = 2000000.0f / (49230 + 49261),
|
|
.reset_limit = 1200, // 600 zeros...
|
|
.decode_fn = &emontx_callback,
|
|
.fields = output_fields,
|
|
};
|