rtl_433/src/devices/ttx201.c
2024-03-08 12:41:35 +01:00

263 lines
9.3 KiB
C

/** @file
Emos TTX201 Thermo Remote Sensor.
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.
*/
/**
Emos TTX201 Thermo Remote Sensor.
Manufacturer: Ewig Industries Macao
Maybe same as Ewig TTX201M (FCC ID: N9ZTTX201M)
IROX ETS69 temperature sensor with DCF77 receiver for EBR606C weather station (Ewig WSA101)
uses the same protocol. It transmits temperature the same way as TTX201 (except for different M bits).
If its internal clock is synchronized to DCF77, it transmits the date/time every hour (:00) instead of
the temperature. The date/time is also transmitted after clock is synced at startup.
Transmit Interval: every ~61 s
Frequency: 433.92 MHz
Manchester Encoding, pulse width: 500 us, interpacket gap width 1500 us.
A complete message is 445 bits:
PPPPPPPP PPPPPPPP P
LL LLKKKKKK IIIIIIII S???BCCC ?XXXTTTT TTTTTTTT MMMMMMMM JJJJ (repeated 7 times)
LL LLKKKKKK IIIIIIII S???BCCC ?XXXTTTT TTTTTTTT MMMMMMMM (last packet without J)
17-bit initial preamble, always 0
PPPPPPPP PPPPPPPP P = 0x00 0x00 0
54-bit data packet format
0 1 2 3 4 5 6 7 8 9 10 11 12 13 (nibbles #, aligned to 8-bit values)
..LL LLKKKKKK IIIIIIII StttBCCC 0XXXTTTT TTTTTTTT MMMMMMMM JJJJ (temperature)
or ..LL LLKKKKKK zyyyyyyy 0tttmmmm dddddHHH HHMMMMMM 0SSSSSS? JJJJ (date/time)
- L = 4-bit start of packet, always 0
- K = 6-bit checksum, sum of nibbles 3-12
- I = 8-bit sensor ID
- S = startup (0 = normal operation, 1 = reset or battery changed)
- t = data type (000 = temperature, 101 = date/time)
- 0 = unknown, always 0
- B = battery status (0 = OK, 1 = low)
- C = 3-bit channel, 0-4
- X = 3-bit packet index, 0-7
- T = 12-bit signed temperature * 10 in Celsius
- M = 8-bit postmark (sensor model?), always 0x14 for TTX201, 0x00 for ETS69
- J = 4-bit packet separator, always 0xF
date/time bit definitions:
- z = time zone/summer time (0 = CET, 1 = CEST)
- y = year
- m = month
- d = day
- H = hour
- M = minute
- S = second
- ? = purpose unknown, always 0 or 1, changes only after reset (battery change)
Sample received raw data package:
bitbuffer:: Number of rows: 10
[00] {17} 00 00 00 : 00000000 00000000 0
[01] {54} 07 30 80 00 42 05 3c
[02] {54} 07 70 80 04 42 05 3c
[03] {54} 07 b0 80 08 42 05 3c
[04] {54} 07 f0 80 0c 42 05 3c
[05] {54} 08 30 80 10 42 05 3c
[06] {54} 08 70 80 14 42 05 3c
[07] {54} 08 b0 80 18 42 05 3c
[08] {50} 08 f0 80 1c 42 05 00 : 00001000 11110000 10000000 00011100 01000010 00000101 00
[09] { 1} 00 : 0
Data decoded:
r cs K ID S B C X T M J
1 28 28 194 0x0 0 0 0 264 0x14 0xf
2 29 29 194 0x0 0 0 1 264 0x14 0xf
3 30 30 194 0x0 0 0 2 264 0x14 0xf
4 31 31 194 0x0 0 0 3 264 0x14 0xf
5 32 32 194 0x0 0 0 4 264 0x14 0xf
6 33 33 194 0x0 0 0 5 264 0x14 0xf
7 34 34 194 0x0 0 0 6 264 0x14 0xf
8 35 35 194 0x0 0 0 7 264 0x14
*/
#include "decoder.h"
#define MSG_PREAMBLE_BITS 17
#define MSG_PACKET_MIN_BITS 50
#define MSG_PACKET_BITS 54
#define MSG_PACKET_POSTMARK 0x14
#define MSG_MIN_ROWS 2
#define MSG_MAX_ROWS 10
#define MSG_PAD_BITS ((((MSG_PACKET_BITS / 8) + 1) * 8) - MSG_PACKET_BITS)
#define MSG_PACKET_LEN ((MSG_PACKET_BITS + MSG_PAD_BITS) / 8)
#define DATA_TYPE_TEMP 0x00
#define DATA_TYPE_DATETIME 0x05
static int checksum_calculate(uint8_t *b)
{
int i;
int sum = 0;
for (i = 1; i < 6; i++) {
sum += ((b[i] & 0xf0) >> 4) + (b[i] & 0x0f);
}
return sum & 0x3f;
}
static int ttx201_decode(r_device *decoder, bitbuffer_t *bitbuffer, unsigned row, unsigned bitpos)
{
uint8_t b[MSG_PACKET_LEN];
int bits = bitbuffer->bits_per_row[row];
int checksum;
int checksum_calculated;
int data_type;
int postmark;
int device_id;
int battery_low;
int channel;
int temperature;
float temperature_c;
data_t *data;
if (bits != MSG_PACKET_MIN_BITS && bits != MSG_PACKET_BITS) {
if (row == 0) {
if (bits < MSG_PREAMBLE_BITS) {
decoder_logf(decoder, 2, __func__, "Short preamble: %d bits (expected %d)",
bits, MSG_PREAMBLE_BITS);
}
} else if (row != (unsigned)bitbuffer->num_rows - 1 && bits == 1) {
decoder_logf(decoder, 2, __func__, "Wrong packet #%u length: %d bits (expected %d)",
row, bits, MSG_PACKET_BITS);
}
return DECODE_ABORT_LENGTH;
}
bitbuffer_extract_bytes(bitbuffer, row, bitpos + MSG_PAD_BITS, b, MSG_PACKET_BITS + MSG_PAD_BITS);
/* Aligned data: LLKKKKKK IIIIIIII S???BCCC ?XXXTTTT TTTTTTTT MMMMMMMM JJJJ */
checksum = b[0] & 0x3f;
checksum_calculated = checksum_calculate(b);
data_type = (b[2] & 0x70) >> 4;
postmark = b[5];
if (decoder_verbose(decoder) > 1) {
decoder_log(decoder, 0, __func__, "TTX201 received raw data");
decoder_log_bitbuffer(decoder, 0, __func__, bitbuffer, "");
decoder_logf(decoder, 0, __func__, "Data decoded:" \
" r cs K ID S B C X T M J\n");
decoder_logf(decoder, 0, __func__, "%2u %2d %2d %3d 0x%01x %1d %1d %1d %4d 0x%02x",
row,
checksum_calculated,
checksum,
b[1], // Device Id
(b[2] & 0xf0) >> 4, // Unknown 1
(b[2] & 0x08) >> 3, // Battery
b[2] & 0x07, // Channel
b[3] >> 4, // Packet index
((int8_t)((b[3] & 0x0f) << 4) << 4) | b[4], // Temperature
postmark);
if (bits == MSG_PACKET_BITS) {
decoder_logf(decoder, 0, __func__, " 0x%01x", b[6] >> 4); // Packet separator
}
decoder_log(decoder, 0, __func__, "");
}
if (checksum != checksum_calculated) {
decoder_logf(decoder, 2, __func__, "Packet #%u checksum error.", row);
return DECODE_FAIL_MIC;
}
if (data_type == DATA_TYPE_DATETIME) {
int cest = b[1] & 0x80;
int year = b[1] & 0x7f;
int month = b[2] & 0x0f;
int day = (b[3] & 0xf8) >> 3;
int hour = (b[3] & 0x07) << 2 | (b[4] & 0xc0) >> 6;
int minute = b[4] & 0x3f;
int second = (b[5] & 0x7e) >> 1;
char clock_str[25];
snprintf(clock_str, sizeof(clock_str), "%04d-%02d-%02dT%02d:%02d:%02d %s", year + 2000, month, day, hour, minute, second, cest ? "CEST" : "CET");
/* clang-format off */
data = data_make(
"model", "", DATA_STRING, "Emos-TTX201",
"radio_clock", "Radio Clock", DATA_STRING, clock_str,
"mic", "Integrity", DATA_STRING, "CHECKSUM",
NULL);
/* clang-format on */
} else { // temperature
device_id = b[1];
battery_low = (b[2] & 0x08) != 0; // if not zero, battery is low
channel = (b[2] & 0x07) + 1;
temperature = (int16_t)(((b[3] & 0x0f) << 12) | (b[4] << 4)); // uses sign extend
temperature_c = (temperature >> 4) * 0.1f;
/* clang-format off */
data = data_make(
"model", "", DATA_STRING, "Emos-TTX201",
"id", "House Code", DATA_INT, device_id,
"channel", "Channel", DATA_INT, channel,
"battery_ok", "Battery", DATA_INT, !battery_low,
"temperature_C", "Temperature", DATA_FORMAT, "%.1f C", DATA_DOUBLE, temperature_c,
"mic", "Integrity", DATA_STRING, "CHECKSUM",
NULL);
/* clang-format on */
}
decoder_output_data(decoder, data);
return 1;
}
/**
Emos TTX201 Thermo Remote Sensor.
@sa ttx201_decode()
*/
static int ttx201_callback(r_device *decoder, bitbuffer_t *bitbuffer)
{
int row;
int ret = 0;
int events = 0;
if (MSG_MIN_ROWS <= bitbuffer->num_rows && bitbuffer->num_rows <= MSG_MAX_ROWS) {
for (row = 0; row < bitbuffer->num_rows; ++row) {
ret = ttx201_decode(decoder, bitbuffer, row, 0);
if (ret > 0)
events += ret;
if (events && !decoder_verbose(decoder))
return events; // for now, break after first successful message
}
}
return events > 0 ? events : ret;
}
static char const *const output_fields[] = {
"model",
"id",
"channel",
"battery_ok",
"temperature_C",
"mic",
"radio_clock",
NULL,
};
r_device const ttx201 = {
.name = "Emos TTX201 Temperature Sensor",
.modulation = OOK_PULSE_MANCHESTER_ZEROBIT,
.short_width = 510,
.long_width = 0, // not used
.reset_limit = 1700,
.tolerance = 250,
.decode_fn = &ttx201_callback,
.fields = output_fields,
};