Skip to content

Commit

Permalink
Add support for Gridstream RF protocol from Landis & Gyr meters
Browse files Browse the repository at this point in the history
  • Loading branch information
krvmk committed Feb 3, 2024
1 parent 2122cc2 commit 7c8d656
Show file tree
Hide file tree
Showing 6 changed files with 298 additions and 6 deletions.
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ See [CONTRIBUTING.md](./docs/CONTRIBUTING.md).
[37]* Inovalley kw9015b, TFA Dostmann 30.3161 (Rain and temperature sensor)
[38] Generic temperature sensor 1
[39] WG-PB12V1 Temperature Sensor
[40] Acurite 592TXR temp/humidity, 592TX temp, 5n1, 3n1, Atlas weather station, 515 fridge/freezer, 6045 lightning, 899 rain, 1190/1192 leak
[40] Acurite 592TXR Temp/Humidity, 592TX Temp, 5n1 Weather Station, 6045 Lightning, 899 Rain, 3N1, Atlas
[41] Acurite 986 Refrigerator / Freezer Thermometer
[42] HIDEKI TS04 Temperature, Humidity, Wind and Rain Sensor
[43] Watchman Sonic / Apollo Ultrasonic / Beckett Rocket oil tank monitor
Expand Down Expand Up @@ -198,7 +198,7 @@ See [CONTRIBUTING.md](./docs/CONTRIBUTING.md).
[110] PMV-107J (Toyota) TPMS
[111] Emos TTX201 Temperature Sensor
[112] Ambient Weather TX-8300 Temperature/Humidity Sensor
[113] Ambient Weather WH31E Thermo-Hygrometer Sensor, EcoWitt WH40 rain gauge, WS68 weather station
[113] Ambient Weather WH31E Thermo-Hygrometer Sensor, EcoWitt WH40 rain gauge
[114] Maverick et73
[115] Honeywell ActivLink, Wireless Doorbell
[116] Honeywell ActivLink, Wireless Doorbell (FSK)
Expand Down Expand Up @@ -337,6 +337,9 @@ See [CONTRIBUTING.md](./docs/CONTRIBUTING.md).
[249] Bresser lightning
[250] Schou 72543 Day Rain Gauge, Motonet MTX Rain, MarQuant Rain Gauge
[251] Fine Offset / Ecowitt WH55 water leak sensor
[252] Gridstream decoder 9.6k
[253] Gridstream decoder 19.2k
[254] Gridstream decoder 38.4k
* Disabled by default, use -R n or a conf file to enable
Expand All @@ -346,7 +349,7 @@ See [CONTRIBUTING.md](./docs/CONTRIBUTING.md).
[-d <RTL-SDR USB device index>] (default: 0)
[-d :<RTL-SDR USB device serial (can be set with rtl_eeprom -s)>]
To set gain for RTL-SDR use -g <gain> to set an overall gain in dB.
SoapySDR device driver is available.
SoapySDR device driver is not available.
[-d ""] Open default SoapySDR device
[-d driver=rtlsdr] Open e.g. specific SoapySDR device
To set gain for SoapySDR use -g ELEM=val,ELEM=val,... e.g. -g LNA=20,TIA=8,PGA=2 (for LimeSDR).
Expand Down
7 changes: 5 additions & 2 deletions conf/rtl_433.example.conf
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ convert si
# protocol 37 # Inovalley kw9015b, TFA Dostmann 30.3161 (Rain and temperature sensor)
protocol 38 # Generic temperature sensor 1
protocol 39 # WG-PB12V1 Temperature Sensor
protocol 40 # Acurite 592TXR temp/humidity, 592TX temp, 5n1, 3n1, Atlas weather station, 515 fridge/freezer, 6045 lightning, 899 rain, 1190/1192 leak
protocol 40 # Acurite 592TXR Temp/Humidity, 592TX Temp, 5n1 Weather Station, 6045 Lightning, 899 Rain, 3N1, Atlas
protocol 41 # Acurite 986 Refrigerator / Freezer Thermometer
protocol 42 # HIDEKI TS04 Temperature, Humidity, Wind and Rain Sensor
protocol 43 # Watchman Sonic / Apollo Ultrasonic / Beckett Rocket oil tank monitor
Expand Down Expand Up @@ -339,7 +339,7 @@ convert si
protocol 110 # PMV-107J (Toyota) TPMS
protocol 111 # Emos TTX201 Temperature Sensor
protocol 112 # Ambient Weather TX-8300 Temperature/Humidity Sensor
protocol 113 # Ambient Weather WH31E Thermo-Hygrometer Sensor, EcoWitt WH40 rain gauge, WS68 weather station
protocol 113 # Ambient Weather WH31E Thermo-Hygrometer Sensor, EcoWitt WH40 rain gauge
protocol 114 # Maverick et73
protocol 115 # Honeywell ActivLink, Wireless Doorbell
protocol 116 # Honeywell ActivLink, Wireless Doorbell (FSK)
Expand Down Expand Up @@ -478,6 +478,9 @@ convert si
protocol 249 # Bresser lightning
protocol 250 # Schou 72543 Day Rain Gauge, Motonet MTX Rain, MarQuant Rain Gauge
protocol 251 # Fine Offset / Ecowitt WH55 water leak sensor
protocol 252 # Gridstream decoder 9.6k
protocol 253 # Gridstream decoder 19.2k
protocol 254 # Gridstream decoder 38.4k

## Flex devices (command line option "-X")

Expand Down
3 changes: 3 additions & 0 deletions include/rtl_433_devices.h
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,9 @@
DECL(bresser_lightning) \
DECL(schou_72543_rain) \
DECL(fineoffset_wh55) \
DECL(gridstream96) \
DECL(gridstream192) \
DECL(gridstream384) \

/* Add new decoders here. */

Expand Down
2 changes: 1 addition & 1 deletion man/man1/rtl_433.1
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ RTL\-SDR device driver is available.
To set gain for RTL\-SDR use \-g <gain> to set an overall gain in dB.
.RE
.RS
SoapySDR device driver is available.
SoapySDR device driver is not available.
.RE
.TP
[ \fB\-d\fI ""\fP ]
Expand Down
1 change: 1 addition & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ add_library(r_433 STATIC
devices/generic_temperature_sensor.c
devices/geo_minim.c
devices/govee.c
devices/gridstream.c
devices/gt_tmbbq05.c
devices/gt_wt_02.c
devices/gt_wt_03.c
Expand Down
282 changes: 282 additions & 0 deletions src/devices/gridstream.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,282 @@
/** @file
Decoder for Gridstream RF devices produced by Landis & Gyr.
Copyright (C) 2023 krvmk
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.
*/

/**
Landis & Gyr Gridstream Power Meters.
- Center Frequency: 915 Mhz
- Frequency Range: 902-928 MHz
- Channel Spacing: 100kHz, 300kHz
- Modulation: FSK-PCM (2-FSK, GFSK)
- Bitrates: 9600, 19200, 38400
- Preamble: 0xAAAA
- Syncword v4: 0b0000000001 0b0111111111
- Syncword v5: 0b0000000001 0b11111111111
This decoder is based on the information from: https://wiki.recessim.com/view/Landis%2BGyr_GridStream_Protocol
Datastream is variable length and bitrate depending on type fields
Preamble
Bytes after preamble are encoded with standard uart settings with start bit, 8 data bits and stop bit.
Data layouts:
Subtype 55:
AAAAAA SSSS TT YY LLLL KK BBBBBBBBBB WWWWWWWWWW II MMMMMMMM KKKK EEEEEEEE KKKK KKKKKK CCCC KKKK XXXX KK
Subtype D2:
AAAAAA SSSS TT YY LL K----------K XXXX
Subtype D5:
AAAAAA SSSS TT YY LLLL KK DDDDDDDD EEEEEEEE II K----------K CCCC KKKK XXXX
- A - Preamble
- S - Syncword
- T - Type
- Y - Subtype
- L - Length
- B - Broadcast
- D - Dest Address
- E - Source Address
- M - Uptime (time since last outage in seconds)
- I - Counter
- C - Clock
- K - Unknown
- X - CRC (poly 0x1021, init set by provider)
*/

#include "decoder.h"
#include <stdbool.h>
#include <stdlib.h>
#include <time.h>

struct crc_init {
uint16_t value;
char const *location;
char const *provider;
};

/*
Decoder will iterate through the known values until checksum is validated.
In order to identify new values, the reveng application, https://reveng.sourceforge.io/,
can determine a missing init value if given several fixed length packet streams.
Subtype 0x55 with a data length of 0x23 can be used for this.
Known CRC init values can be added to the code via PR when they have been identified.
*/
static struct crc_init known_crc_init[] = {
{0xe623, "Kansas City, MO", "Evergy-Missouri West"},
{0x5fd6, "Dallas, TX", "Oncor"},
{0xD553, "Austin, TX", "Austin Energy"},
{0x45F8, "Dallas, TX", "CoServ"},
{0x62C1, "Quebec, CAN", "Hydro-Quebec"},
{0x23D1, "Seattle, WA", "Seattle City Light"},
{0x2C22, "Santa Barbara, CA", "Southern California Edison"},
{0x142A, "Washington", "Puget Sound Energy"},
{0x47F7, "Pennsylvania", "PPL Electric"},
{0x22c6, "Long Island, NY", "PSEG Long Island"}};

static int gridstream_checksum(int fulllength, uint16_t length, uint8_t *bits, int adjust)
{
uint16_t crc_count = 0;
bool crc_ok = false;
uint16_t crc;

if ((fulllength - 4 + adjust) < length) {
return DECODE_ABORT_LENGTH;
}
crc = (bits[2 + length + adjust] << 8) | bits[3 + length + adjust];
do {
if (crc16(&bits[4 + adjust], length - 2, 0x1021, known_crc_init[crc_count].value) == crc) {
crc_ok = true;
}
else {
crc_count++;
}
} while (crc_count < (sizeof(known_crc_init) / sizeof(struct crc_init)) && crc_ok == false);
if (!crc_ok) {
return DECODE_FAIL_MIC;
}
else {
return crc_count;
}
}

static int gridstream_decode(r_device *decoder, bitbuffer_t *bitbuffer)
{
data_t *data;
uint8_t const preambleV4[] = {
0xAA,
0xAA,
0x00,
0x5F,
0xF0,
};
uint8_t const preambleV5[] = {
0xAA,
0xAA,
0x00,
0x7F,
0xF8,
};
/* Maximum data length is not yet known, but 256 should be a sufficient buffer size. */
uint8_t b[256];
uint16_t stream_len;
char found_crc[5] = "";
char destwanaddress_str[13] = "";
char srcwanaddress_str[13] = "";
char srcaddress_str[9] = "";
char destaddress_str[9] = "";
bool srcwanaddress = false;
uint32_t uptime = 0;
time_t clock;
char clock_str[80];
int subtype;
unsigned offset;
int protocol_version;
int subtype_mod = 0;
int crcidx;
int decoded_len;
offset = bitbuffer_search(bitbuffer, 0, 0, preambleV4, 36);
if (offset >= bitbuffer->bits_per_row[0]) {
offset = bitbuffer_search(bitbuffer, 0, 0, preambleV5, 37);
if (offset >= bitbuffer->bits_per_row[0]) {
return DECODE_FAIL_SANITY;
}
else {
decoded_len = extract_bytes_uart(bitbuffer->bb[0], offset + 37, bitbuffer->bits_per_row[0] - offset - 37, b);
protocol_version = 5;
}
}
else {
decoded_len = extract_bytes_uart(bitbuffer->bb[0], offset + 36, bitbuffer->bits_per_row[0] - offset - 36, b);
protocol_version = 4;
}
if (decoded_len >= 5) {
switch (b[0]) {
case 0x2A:
subtype = b[1];
if (subtype == 0xD2) {
stream_len = b[2];
subtype_mod = -1;
}
else {
stream_len = (b[2] << 8) | b[3];
}
crcidx = gridstream_checksum(decoded_len, stream_len, b, subtype_mod);
if (crcidx < 0) {
decoder_log(decoder, 1, __func__, "Bad CRC or unknown init value. ");
if ((stream_len == 0x23) && (subtype = 0xAA)) {
/* These data types can be used to find new init values. See comment block on line 67. */
decoder_log_bitrow(decoder, 1, __func__, &b[4], decoded_len * 8, "Use RevEng to find init value.");
}
return DECODE_FAIL_MIC;
}
sprintf(found_crc, "%04x", known_crc_init[crcidx].value);
switch (subtype) {
case 0x55:
sprintf(destwanaddress_str, "%02x%02x%02x%02x%02x%02x", b[5], b[6], b[7], b[8], b[9], b[10]);
sprintf(srcwanaddress_str, "%02x%02x%02x%02x%02x%02x", b[11], b[12], b[13], b[14], b[15], b[16]);
srcwanaddress = true;
sprintf(srcaddress_str, "%02x%02x%02x%02x", b[24], b[25], b[26], b[27]);
uptime = ((uint32_t)b[18] << 24) | (b[19] << 16) | (b[20] << 8) | b[21];
break;
case 0xD5:
sprintf(destaddress_str, "%02x%02x%02x%02x", b[5], b[6], b[7], b[8]);
sprintf(srcaddress_str, "%02x%02x%02x%02x", b[9], b[10], b[11], b[12]);
if (stream_len == 0x47) {
clock = ((uint32_t)b[14] << 24) | (b[15] << 16) | (b[16] << 8) | b[17];
uptime = ((uint32_t)b[22] << 24) | (b[23] << 16) | (b[24] << 8) | b[25];
sprintf(srcwanaddress_str, "%02x%02x%02x%02x%02x%02x", b[30], b[31], b[32], b[33], b[34], b[35]);
srcwanaddress = true;
strftime(clock_str, sizeof(clock_str), "%a %Y-%m-%d %H:%M:%S %Z", localtime(&clock));
}
break;
}

/* clang-format off */
data = data_make(
"model", "", DATA_STRING, "LandisGyr-GS",
"networkID", "Network ID", DATA_STRING, found_crc,
"location", "Location", DATA_STRING, known_crc_init[crcidx].location,
"provider", "Provider", DATA_STRING, known_crc_init[crcidx].provider,
"subtype", "", DATA_INT, subtype,
"protoversion", "", DATA_INT, protocol_version,
"mic", "Integrity", DATA_STRING, "CRC",
"id", "Source Meter ID", DATA_COND, subtype != 0xD2, DATA_STRING, srcaddress_str,
"wanaddress", "Source Meter WAN ID", DATA_COND, srcwanaddress, DATA_STRING, srcwanaddress_str,
"destaddress", "Target Meter WAN ID", DATA_COND, subtype == 0x55, DATA_STRING, destwanaddress_str,
"destaddress", "Target Meter ID", DATA_COND, subtype == 0xD5, DATA_STRING, destaddress_str,
"timestamp", "Timestamp", DATA_COND, subtype == 0xD5 && stream_len == 0x47, DATA_STRING, clock_str,
"uptime", "Uptime", DATA_COND, uptime > 0, DATA_INT, uptime,
NULL);
/* clang-format on */

decoder_output_data(decoder, data);
break;
}
decoder_log_bitrow(decoder, 0, __func__, b, decoded_len * 8, "Decoded frame data");
// Return 1 if message successfully decoded
return 1;
}
else {
return DECODE_FAIL_SANITY;
}
}

static char const *const output_fields[] = {
"model",
"networkID",
"location",
"provider",
"id",
"subtype",
"wanaddress",
"destaddress",
"uptime",
"srclocation",
"destlocation",
"timestamp",
"protoversion",
"framedata",
"mic",
NULL,
};

r_device const gridstream96 = {
.name = "Gridstream decoder 9.6k",
.modulation = FSK_PULSE_PCM,
.short_width = 104,
.long_width = 104,
.reset_limit = 20000,
.decode_fn = &gridstream_decode,
.disabled = 0,
.fields = output_fields,
};

r_device const gridstream192 = {
.name = "Gridstream decoder 19.2k",
.modulation = FSK_PULSE_PCM,
.short_width = 52,
.long_width = 52,
.reset_limit = 20000,
.decode_fn = &gridstream_decode,
.disabled = 0,
.fields = output_fields,
};

r_device const gridstream384 = {
.name = "Gridstream decoder 38.4k",
.modulation = FSK_PULSE_PCM,
.short_width = 22,
.long_width = 22,
.reset_limit = 20000,
.decode_fn = &gridstream_decode,
.disabled = 0,
.fields = output_fields,
};

0 comments on commit 7c8d656

Please sign in to comment.