1 /** @file
2     inFactory outdoor temperature and humidity sensor.
3 
4     Copyright (C) 2017 Sirius Weiß <siriuz@gmx.net>
5     Copyright (C) 2017 Christian W. Zuckschwerdt <zany@triq.net>
6     Copyright (C) 2020 Hagen Patzke <hpatzke@gmx.net>
7 
8     This program is free software; you can redistribute it and/or modify
9     it under the terms of the GNU General Public License as published by
10     the Free Software Foundation; either version 2 of the License, or
11     (at your option) any later version.
12 */
13 /**
14 Outdoor sensor, transmits temperature and humidity data
15 - inFactory NC-3982-913/NX-5817-902, Pearl (for FWS-686 station)
16 - nor-tec 73383 (weather station + sensor), Schou Company AS, Denmark
17 - DAY 73365 (weather station + sensor), Schou Company AS, Denmark
18 
19 Known brand names: inFactory, nor-tec, GreenBlue, DAY. Manufacturer in China.
20 
21 
22 Transmissions includes an id. Every 60 seconds the sensor transmits 6 packets:
23 
24     0000 1111 | 0011 0000 | 0101 1100 | 1110 0111 | 0110 0001
25     iiii iiii | cccc ub?? | tttt tttt | tttt hhhh | hhhh ??nn
26 
27 - i: identification // changes on battery switch
28 - c: CRC-4 // CCITT checksum, see below for computation specifics
29 - u: unknown // (sometimes set at power-on, but not always)
30 - b: battery low // flag to indicate low battery voltage
31 - h: Humidity // BCD-encoded, each nibble is one digit, 'A0' means 100%rH
32 - t: Temperature // in °F as binary number with one decimal place + 90 °F offset
33 - n: Channel // Channel number 1 - 3
34 
35 Usage:
36 
37     # rtl_433 -f 434052000 -R 91 -F json:log.json
38     # rtl_433 -R 91 -F json:log.json
39     # rtl_433 -C si
40 
41 Payload looks like this:
42 
43     [00] {40} 0f 30 5c e7 61 : 00001111 00110000 01011100 11100111 01100001
44 
45 (See below for more information about the signal timing.)
46 */
47 
48 #include "decoder.h"
49 
infactory_crc_check(uint8_t * b)50 static int infactory_crc_check(uint8_t *b) {
51     uint8_t msg_crc, crc, msg[5];
52     memcpy(msg, b, 5);
53     msg_crc = msg[1] >> 4;
54     // for CRC computation, channel bits are at the CRC position(!)
55     msg[1] = (msg[1] & 0x0F) | (msg[4] & 0x0F) << 4;
56     // crc4() only works with full bytes
57     crc = crc4(msg, 4, 0x13, 0); // Koopmann 0x9, CCITT-4; FP-4; ITU-T G.704
58     crc ^= msg[4] >> 4; // last nibble is only XORed
59     return (crc == msg_crc);
60 }
61 
infactory_callback(r_device * decoder,bitbuffer_t * bitbuffer)62 static int infactory_callback(r_device *decoder, bitbuffer_t *bitbuffer)
63 {
64     if (bitbuffer->bits_per_row[0] != 40)
65         return DECODE_ABORT_LENGTH;
66 
67     uint8_t *b = bitbuffer->bb[0];
68 
69     /* Check that the last 4 bits of message are not 0 (channel number 1 - 3) */
70     if (!(b[4]&0x0F))
71         return DECODE_ABORT_EARLY;
72 
73     if (!infactory_crc_check(b))
74         return DECODE_FAIL_MIC;
75 
76     int id          = b[0];
77     int battery_low = (b[1] >> 2) & 1;
78     int temp_raw    = (b[2] << 4) | (b[3] >> 4);
79     int humidity    = (b[3] & 0x0F) * 10 + (b[4] >> 4); // BCD, 'A0'=100%rH
80     int channel     = b[4] & 0x03;
81 
82     float temp_f    = (float)temp_raw * 0.1 - 90;
83 
84     /* clang-format off */
85     data_t *data = data_make(
86             "model",            "",             DATA_STRING, "inFactory-TH",
87             "id",               "ID",           DATA_INT,    id,
88             "channel",          "Channel",      DATA_INT,    channel,
89             "battery_ok",       "Battery",      DATA_INT,    !battery_low,
90             "temperature_F",    "Temperature",  DATA_FORMAT, "%.02f F", DATA_DOUBLE, temp_f,
91             "humidity",         "Humidity",     DATA_FORMAT, "%u %%", DATA_INT, humidity,
92             "mic",              "Integrity",    DATA_STRING, "CRC",
93             NULL);
94     /* clang-format on */
95 
96     decoder_output_data(decoder, data);
97     return 1;
98 }
99 
100 static char *output_fields[] = {
101         "model",
102         "id",
103         "channel",
104         "battery_ok",
105         "temperature_F",
106         "humidity",
107         "mic",
108         NULL,
109 };
110 
111 /*
112 Analysis using Genuino (see http://gitlab.com/hp-uno, e.g. uno_log_433):
113 
114 Observed On-Off-Key (OOK) data pattern:
115 
116     preamble            syncPrefix        data...(40 bit)                        syncPostfix
117     HHLL HHLL HHLL HHLL HLLLLLLLLLLLLLLLL (HLLLL HLLLLLLLL HLLLL HLLLLLLLL ....) HLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL
118 
119 Breakdown:
120 
121 - four preamble pairs '1'/'0' each with a length of ca. 1000us
122 - syncPre, syncPost, data0, data1 have a '1' start pulse of ca. 500us
123 - syncPre pulse before dataPtr has a '0' pulse length of ca. 8000us
124 - data0 (0-bits) have then a '0' pulse length of ca. 2000us
125 - data1 (1-bits) have then a '0' pulse length of ca. 4000us
126 - syncPost after dataPtr has a '0' pulse length of ca. 16000us
127 
128 This analysis is the reason for the new r_device definitions below.
129 NB: pulse_demod_ppm does not use .gap_limit if .tolerance is set.
130 */
131 
132 r_device infactory = {
133         .name        = "inFactory, nor-tec, FreeTec NC-3982-913 temperature humidity sensor",
134         .modulation  = OOK_PULSE_PPM,
135         .sync_width  = 500,   // Sync pulse width (recognized, but not used)
136         .short_width = 2000,  // Width of a '0' gap
137         .long_width  = 4000,  // Width of a '1' gap
138         .reset_limit = 5000,  // Maximum gap size before End Of Message [us]
139         .tolerance   = 750,   // Width interval 0=[1250..2750] 1=[3250..4750], should be quite robust
140         .decode_fn   = &infactory_callback,
141         .disabled    = 0,
142         .fields      = output_fields,
143 };
144