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