Редизайн на основе текущей ветки мейнстрима + новые устройства.
[rtl-433.git] / src / devices / fineoffset.c
diff --git a/src/devices/fineoffset.c b/src/devices/fineoffset.c
new file mode 100644 (file)
index 0000000..83b4b6d
--- /dev/null
@@ -0,0 +1,340 @@
+/* Fine Offset Electronics sensor protocol
+ *
+ * Copyright (C) 2017 Tommy Vestermark
+ * 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 "rtl_433.h"
+#include "data.h"
+#include "util.h"
+#include "pulse_demod.h"
+
+
+/*
+ * Fine Offset Electronics WH2 Temperature/Humidity sensor protocol
+ * aka Agimex Rosenborg 66796 (sold in Denmark)
+ * aka ClimeMET CM9088 (Sold in UK)
+ * aka TFA Dostmann/Wertheim 30.3157 (Temperature only!) (sold in Germany)
+ * aka ...
+ *
+ * The sensor sends two identical packages of 48 bits each ~48s. The bits are PWM modulated with On Off Keying
+ *
+ * The data is grouped in 6 bytes / 12 nibbles
+ * [pre] [pre] [type] [id] [id] [temp] [temp] [temp] [humi] [humi] [crc] [crc]
+ *
+ * pre is always 0xFF
+ * type is always 0x4 (may be different for different sensor type?)
+ * id is a random id that is generated when the sensor starts
+ * temp is 12 bit signed magnitude scaled by 10 celcius
+ * humi is 8 bit relative humidity percentage
+ * 
+ * Based on reverse engineering with gnu-radio and the nice article here:
+ *  http://lucsmall.com/2012/04/29/weather-station-hacking-part-2/
+ */
+static int fineoffset_WH2_callback(bitbuffer_t *bitbuffer) {
+    bitrow_t *bb = bitbuffer->bb;
+    data_t *data;
+
+    char time_str[LOCAL_TIME_BUFLEN];
+
+    if (debug_output > 1) {
+       fprintf(stderr,"Possible fineoffset: ");
+       bitbuffer_print(bitbuffer);
+    }
+
+    uint8_t id;
+    int16_t temp;
+    float temperature;
+    uint8_t humidity;
+
+    const uint8_t polynomial = 0x31;    // x8 + x5 + x4 + 1 (x8 is implicit)
+
+    // Validate package
+    if (bitbuffer->bits_per_row[0] == 48 &&         // Match exact length to avoid false positives
+        bb[0][0] == 0xFF &&             // Preamble
+        bb[0][5] == crc8(&bb[0][1], 4, polynomial, 0)  // CRC (excluding preamble)
+    )
+    {
+        /* Get time now */
+        local_time_str(0, time_str);
+
+         // Nibble 3,4 contains id
+        id = ((bb[0][1]&0x0F) << 4) | ((bb[0][2]&0xF0) >> 4);
+
+        // Nibble 5,6,7 contains 12 bits of temperature
+        // The temperature is signed magnitude and scaled by 10
+        temp = ((bb[0][2] & 0x0F) << 8) | bb[0][3];
+        if(temp & 0x800) {
+            temp &= 0x7FF;     // remove sign bit
+            temp = -temp;      // reverse magnitude
+        }
+        temperature = (float)temp / 10;
+
+        // Nibble 8,9 contains humidity
+        humidity = bb[0][4];
+
+
+        if (debug_output > 1) {
+           fprintf(stderr, "ID          = 0x%2X\n",  id);
+           fprintf(stderr, "temperature = %.1f C\n", temperature);
+           fprintf(stderr, "humidity    = %u %%\n",  humidity);
+        }
+
+        // Thermo
+        if (bb[0][4] == 0xFF) {
+        data = data_make("time",          "",            DATA_STRING, time_str,
+                         "model",         "",            DATA_STRING, "TFA 30.3157 Temperature sensor",
+                         "id",            "ID",          DATA_INT, id,
+                         "temperature_C", "Temperature", DATA_FORMAT, "%.01f C", DATA_DOUBLE, temperature,
+                          NULL);
+        data_acquired_handler(data);
+        }
+        // Thermo/Hygro
+        else {
+        data = data_make("time",          "",            DATA_STRING, time_str,
+                         "model",         "",            DATA_STRING, "Fine Offset Electronics, WH2 Temperature/Humidity sensor",
+                         "id",            "ID",          DATA_INT, id,
+                         "temperature_C", "Temperature", DATA_FORMAT, "%.01f C", DATA_DOUBLE, temperature,
+                         "humidity",      "Humidity",    DATA_FORMAT, "%u %%", DATA_INT, humidity,
+                          NULL);
+        data_acquired_handler(data);
+        }
+        return 1;
+    }
+    return 0;
+}
+
+
+/* Fine Offset Electronics WH25 Temperature/Humidity/Pressure sensor protocol
+ *
+ * The sensor sends a package each ~64 s with a width of ~28 ms. The bits are PCM modulated with Frequency Shift Keying
+ *
+ * Example:
+ * [00] {500} 80 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 2a aa aa aa aa aa 8b 75 39 40 9c 8a 09 c8 72 6e ea aa aa 80 10 
+ * Reading: 22.6 C, 40 %, 1001.7 hPa
+ *
+ * Extracted data:
+ *          ?I IT TT HH PP PP CC BB
+ * aa 2d d4 e5 02 72 28 27 21 c9 bb aa
+ *
+ * II = Sensor ID (based on 2 different sensors). Does not change at battery change.
+ * T TT = Temperature (+40*10)
+ * HH = Humidity
+ * PP PP = Pressure (*10)
+ * CC = Checksum of previous 6 bytes (binary sum truncated to 8 bit)
+ * BB = Bitsum (XOR) of the 6 data bytes (high and low nibble exchanged)
+ *
+ */
+static int fineoffset_WH25_callback(bitbuffer_t *bitbuffer) {
+    data_t *data;
+    char time_str[LOCAL_TIME_BUFLEN];
+
+    // Validate package
+    if (bitbuffer->bits_per_row[0] < 440 || bitbuffer->bits_per_row[0] > 510) {  // Nominal size is 488 bit periods
+        return 0;
+    }
+
+    // Get time now
+    local_time_str(0, time_str);
+
+    // Find a data package and extract data buffer
+    static const uint8_t HEADER[] = { 0xAA, 0x2D, 0xD4 };
+    uint8_t buffer[12];
+    unsigned bit_offset = bitbuffer_search(bitbuffer, 0, 320, HEADER, sizeof(HEADER)*8);    // Normal index is 367, skip some bytes to find faster
+    if (bit_offset + sizeof(buffer)*8 >= bitbuffer->bits_per_row[0]) {  // Did not find a big enough package
+        if (debug_output) {
+            fprintf(stderr, "Fineoffset_WH25: short package. Header index: %u\n", bit_offset);
+            bitbuffer_print(bitbuffer);
+        }
+        return 0;
+    }
+    bitbuffer_extract_bytes(bitbuffer, 0, bit_offset, buffer, sizeof(buffer)*8);
+
+    if (debug_output) {
+        char raw_str[128];
+        for (unsigned n=0; n<sizeof(buffer); n++) { sprintf(raw_str+n*3, "%02x ", buffer[n]); }
+        fprintf(stderr, "Fineoffset_WH25: Raw: %s @ bit_offset [%u]\n", raw_str, bit_offset);
+    }
+
+    // Verify checksum
+    uint8_t checksum = 0, bitsum = 0;
+    for (size_t n=3; n<=8; ++n) {
+        checksum += buffer[n];
+        bitsum ^= buffer[n];
+    }
+    bitsum = (bitsum << 4) | (bitsum >> 4);     // Swap nibbles
+    if (checksum != buffer[9] || bitsum != buffer[10]) {
+        if (debug_output) {
+            fprintf(stderr, "Fineoffset_WH25: Checksum error: %02x %02x\n", checksum, bitsum);
+            bitbuffer_print(bitbuffer);
+        }
+        return 0;
+    }
+
+    // Decode data
+    uint8_t id = (buffer[3] << 4) | (buffer[4] >> 4);
+    float   temperature = (float)((uint16_t)(buffer[4] & 0xF) << 8 | buffer[5]) / 10.0 - 40.0;
+    uint8_t humidity = buffer[6];
+    float   pressure = (float)((uint16_t)buffer[7] << 8 | buffer[8]) / 10.0;
+
+    // Output data
+    data = data_make("time",          "",            DATA_STRING, time_str,
+                     "model",         "",            DATA_STRING, "Fine Offset Electronics, WH25",
+                     "id",            "ID",          DATA_INT, id,
+                     "temperature_C", "Temperature", DATA_FORMAT, "%.01f C", DATA_DOUBLE, temperature,
+                     "humidity",      "Humidity",    DATA_FORMAT, "%u %%", DATA_INT, humidity,
+                     "pressure",      "Pressure",    DATA_FORMAT, "%.01f hPa", DATA_DOUBLE, pressure,
+                      NULL);
+    data_acquired_handler(data);
+
+    return 1;
+}
+
+
+/* Fine Offset Electronics WH0530 Temperature/Rain sensor protocol
+ * aka Agimex Rosenborg 35926 (sold in Denmark)
+ * aka ...
+ *
+ * The sensor sends two identical packages of 71 bits each ~48s. The bits are PWM modulated with On Off Keying
+ * Data consists of 9 bytes with first bit missing
+ *
+ * Extracted data:
+ * 7f 38 a2 8f 02 00 ff e7 51
+ * hh hI IT TT RR RR ?? CC CC
+ *
+ * hh h = Header (first bit is not received and must be added)
+ * II = Sensor ID (guess). Does not change at battery change.
+ * T TT = Temperature (+40*10)
+ * RR RR = Rain count (each count = 0.3mm, LSB first)
+ * ?? = Always 0xFF (maybe reserved for humidity?)
+ * CC = CRC8 with polynomium 0x31
+ * CC = Checksum of previous 7 bytes (binary sum truncated to 8 bit)
+ */
+static int fineoffset_WH0530_callback(bitbuffer_t *bitbuffer) {
+    bitrow_t *bb = bitbuffer->bb;
+    data_t *data;
+
+    char time_str[LOCAL_TIME_BUFLEN];
+
+    // Validate package
+    if (bitbuffer->bits_per_row[0] != 71        // Match exact length to avoid false positives
+        || (bb[0][0]>>1) != 0x7F                // Check header (two upper nibbles)
+        || (bb[0][1]>>5) != 0x3                 // Check header (third nibble)
+    ) {
+        return 0;
+    }
+
+    // Get time now
+    local_time_str(0, time_str);
+
+    uint8_t buffer[8];
+    bitbuffer_extract_bytes(bitbuffer, 0, 7, buffer, sizeof(buffer)*8);     // Skip first 7 bits
+
+    if (debug_output) {
+        char raw_str[128];
+        for (unsigned n=0; n<sizeof(buffer); n++) { sprintf(raw_str+n*3, "%02x ", buffer[n]); }
+        fprintf(stderr, "Fineoffset_WH0530: Raw %s\n", raw_str);
+    }
+
+    // Verify checksum
+    const uint8_t crc = crc8(buffer, 6, 0x31, 0);
+    const uint8_t checksum = buffer[0] + buffer[1] + buffer[2] + buffer[3] + buffer[4] + buffer[5] + buffer[6];
+    if (crc != buffer[6] || checksum != buffer[7]) {
+        if (debug_output) {
+            fprintf(stderr, "Fineoffset_WH0530: Checksum error: %02x %02x\n", crc, checksum);
+        }
+        return 0;
+    }
+
+    const uint8_t id = (buffer[0]<<4) | (buffer[1]>>4);
+    const float temperature = (float)((uint16_t)(buffer[1] & 0xF)<< 8 | buffer[2]) / 10.0 - 40.0;
+    const float rain = 0.3 * (((uint16_t)buffer[4] << 8) | buffer[3]);
+
+    data = data_make("time",          "",            DATA_STRING, time_str,
+                     "model",         "",            DATA_STRING, "Fine Offset Electronics, WH0530 Temperature/Rain sensor",
+                     "id",            "ID",          DATA_INT, id,
+                     "temperature_C", "Temperature", DATA_FORMAT, "%.01f C", DATA_DOUBLE, temperature,
+                     "rain",          "Rain",        DATA_FORMAT, "%.01f mm", DATA_DOUBLE, rain,
+                     NULL);
+    data_acquired_handler(data);
+
+    return 1;
+}
+
+
+static char *output_fields[] = {
+    "time",
+    "model",
+    "id",
+    "temperature_C",
+    "humidity",
+    NULL
+};
+
+
+static char *output_fields_WH25[] = {
+    "time",
+    "model",
+    "id",
+    "temperature_C",
+    "humidity",
+    "pressure",
+//    "raw",
+    NULL
+};
+
+
+static char *output_fields_WH0530[] = {
+    "time",
+    "model",
+    "id",
+    "temperature_C",
+    "rain",
+    NULL
+};
+
+
+r_device fineoffset_WH2 = {
+    .name           = "Fine Offset Electronics, WH2 Temperature/Humidity Sensor",
+    .modulation     = OOK_PULSE_PWM_RAW,
+    .short_limit    = 800,     // Short pulse 544µs, long pulse 1524µs, fixed gap 1036µs
+    .long_limit     = 2800,    // Maximum pulse period (long pulse + fixed gap)
+    .reset_limit    = 2800,    // We just want 1 package
+    .json_callback  = &fineoffset_WH2_callback,
+    .disabled       = 0,
+    .demod_arg      = 0,
+    .fields         = output_fields
+};
+
+
+r_device fineoffset_WH25 = {
+    .name           = "Fine Offset Electronics, WH25 Temperature/Humidity/Pressure Sensor",
+    .modulation     = FSK_PULSE_PCM,
+    .short_limit    = 58,      // Bit width = 58µs (measured across 580 samples / 40 bits / 250 kHz )
+    .long_limit     = 58,      // NRZ encoding (bit width = pulse width)
+    .reset_limit    = 20000,   // Package starts with a huge gap of ~18900 us
+    .json_callback  = &fineoffset_WH25_callback,
+    .disabled       = 0,
+    .demod_arg      = 0,
+    .fields         = output_fields_WH25
+};
+
+
+PWM_Precise_Parameters pwm_precise_parameters_fo_wh0530 = {
+    .pulse_tolerance    = 40,
+    .pulse_sync_width   = 0,    // No sync bit used
+};
+
+r_device fineoffset_WH0530 = {
+    .name           = "Fine Offset Electronics, WH0530 Temperature/Rain Sensor",
+    .modulation     = OOK_PULSE_PWM_PRECISE,
+    .short_limit    = 504,     // Short pulse 504µs
+    .long_limit     = 1480, // Long pulse 1480µs
+    .reset_limit    = 1200,    // Fixed gap 960µs (We just want 1 package)
+    .json_callback  = &fineoffset_WH0530_callback,
+    .disabled       = 0,
+    .demod_arg      = (uintptr_t)&pwm_precise_parameters_fo_wh0530,
+    .fields         = output_fields_WH0530
+};