/** @file
    Wireless Smoke & Heat Detector.

    Copyright (C) 2017 Christian W. Zuckschwerdt <zany@triq.net>

    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.

*/
/**
Wireless Smoke & Heat Detector.

Ningbo Siterwell Electronics  GS 558  Sw. V05  Ver. 1.3  on 433.885MHz
VisorTech RWM-460.f  Sw. V05, distributed by PEARL, seen on 433.674MHz

A short wakeup pulse followed by a wide gap (11764 us gap),
followed by 24 data pulses and 2 short stop pulses (in a single bit width).
This is repeated 8 times with the next wakeup directly following
the preceding stop pulses.

Bit width is 1731 us with
Short pulse: -___ 436 us pulse + 1299 us gap
Long pulse:  ---_ 1202 us pulse + 526 us gap
Stop pulse:  -_-_ 434us pulse + 434us gap + 434us pulse + 434us gap
= 2300 baud pulse width / 578 baud bit width

24 bits (6 nibbles):
- first 5 bits are unit number with bits reversed
- next 15(?) bits are group id, likely also reversed
- last 4 bits are always 0x3 (maybe hardware/protocol version)
Decoding will reverse the whole packet.
Short pulses are 0, long pulses 1, need to invert the demod output.

Each device has it's own group id and unit number as well as a
shared/learned group id and unit number.
In learn mode the primary will offer it's group id and the next unit number.
The secondary device acknowledges pairing with 16 0x555555 packets
and copies the offered shared group id and unit number.
The primary device then increases it's unit number.
This means the primary will always have the same unit number as the
last learned secondary, weird.
Also you always need to learn from the same primary.

*/

#include "decoder.h"

static int smoke_gs558_callback(r_device *decoder, bitbuffer_t *bitbuffer)
{
    uint8_t *b;
    int r;
    int learn = 0;
    int unit; // max 30
    int id;

    if (bitbuffer->num_rows < 3)
        return DECODE_ABORT_EARLY; // truncated transmission

    bitbuffer_invert(bitbuffer);

    for (r = 0; r < bitbuffer->num_rows; ++r) {
        b = bitbuffer->bb[r];

        // count learn pattern and strip
        if (bitbuffer->bits_per_row[r] >= 24
                && b[0] == 0x55 && b[1] == 0x55 && b[2] == 0x55) {
            ++learn;
            bitbuffer->bits_per_row[r] = 0;
        }

        // strip end-of-packet pulse
        if ((bitbuffer->bits_per_row[r] == 26 || bitbuffer->bits_per_row[r] == 27)
                && b[3] == 0)
            bitbuffer->bits_per_row[r] = 24;
    }

    r = bitbuffer_find_repeated_row(bitbuffer, 3, 24);

    if (r < 0)
        return DECODE_ABORT_EARLY;

    if (bitbuffer->bits_per_row[r] > 4 * 8)
        return DECODE_ABORT_LENGTH;

    b = bitbuffer->bb[r];

    // if ((b[2] & 0x0f) != 0x03)
    //     return DECODE_ABORT_EARLY; // last nibble is always 0x3?

    b[0] = reverse8(b[0]);
    b[1] = reverse8(b[1]);
    b[2] = reverse8(b[2]);

    unit = b[0] & 0x1f; // 5 bits
    id = ((b[2] & 0x0f) << 11) | (b[1] << 3) | (b[0] >> 5); // 15 bits

    if (id == 0 || id == 0x7fff)
        return DECODE_FAIL_SANITY; // reject min/max to reduce false positives

    char code_str[7];
    snprintf(code_str, sizeof(code_str), "%02x%02x%02x", b[2], b[1], b[0]);

    /* clang-format off */
    data_t *data = data_make(
            "model",        "",             DATA_STRING, "Smoke-GS558",
            "id"   ,        "",             DATA_INT, id,
            "unit",         "",             DATA_INT, unit,
            "learn",        "",             DATA_INT, learn > 1,
            "code",         "Raw Code",     DATA_STRING, code_str,
            NULL);
    /* clang-format on */
    decoder_output_data(decoder, data);

    return 1;
}

static char const *const output_fields[] = {
        "model",
        "id",
        "unit",
        "learn",
        "code",
        NULL,
};

r_device const smoke_gs558 = {
        .name        = "Wireless Smoke and Heat Detector GS 558",
        .modulation  = OOK_PULSE_PWM,
        .short_width = 436,          // Threshold between short and long pulse [us]
        .long_width  = 1202,         // Maximum gap size before new row of bits [us]
        .gap_limit   = 1299 * 1.5f,  // Maximum gap size before new row of bits [us]
        .reset_limit = 11764 * 1.2f, // Maximum gap size before End Of Message [us]
        .decode_fn   = &smoke_gs558_callback,
        .fields      = output_fields,
};
