/*
 * dht11_reader.c
 *
 * Standalone DHT11 reader using libgpiod (C).
 * Uses open-drain output for the host start signal and a tight
 * user-space polling loop to sample the line at ~1us intervals.
 *
 * Usage:
 *   ./dht11_reader [chip] [line]
 *
 * Defaults:
 *   chip = gpiochip0
 *   line = 19 (RK3562 GPIO0_C3_d on the 20-pin header pin 2)
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <errno.h>
#include <sched.h>
#include <gpiod.h>

#define DEFAULT_CHIP    "gpiochip0"
#define DEFAULT_LINE    19

/* DHT11 timing (microseconds) */
#define START_LOW_US            20000   /* 18-30 ms */
#define RELEASE_MIN_US          20      /* host release must be >= 20us */
#define RESPONSE_TIMEOUT_US     200
#define BIT_TIMEOUT_US          120
#define BIT_THRESHOLD_US        40      /* high > 40us -> 1 */

#define MAX_SAMPLES             8192

struct sample {
    uint64_t ts_us;
    int      val;
};

static inline uint64_t now_us(void)
{
    struct timespec ts;
    clock_gettime(CLOCK_MONOTONIC, &ts);
    return (uint64_t)ts.tv_sec * 1000000ULL + (uint64_t)ts.tv_nsec / 1000ULL;
}

static inline void busy_wait_us(uint64_t us)
{
    uint64_t deadline = now_us() + us;
    while (now_us() < deadline) {
        __asm__ volatile ("nop");
    }
}

static void usage(const char *prog)
{
    fprintf(stderr, "Usage: %s [chip] [line]\n", prog);
    fprintf(stderr, "  chip: gpio chip name (default: %s)\n", DEFAULT_CHIP);
    fprintf(stderr, "  line: gpio line offset (default: %d, RK3562 GPIO0_C3_d / header pin 2)\n", DEFAULT_LINE);
}

static void rt_setup(void)
{
    struct sched_param param = { .sched_priority = 50 };
    if (sched_setscheduler(0, SCHED_FIFO, &param) < 0) {
        /* non-fatal */
    }
}

int main(int argc, char *argv[])
{
    const char *chip_name = DEFAULT_CHIP;
    unsigned int line_offset = DEFAULT_LINE;

    if (argc >= 2) chip_name = argv[1];
    if (argc >= 3) line_offset = (unsigned int)atoi(argv[2]);
    if (argc > 3) { usage(argv[0]); return 2; }

    rt_setup();

    struct gpiod_chip *chip = gpiod_chip_open_by_name(chip_name);
    if (!chip) {
        fprintf(stderr, "ERR: failed to open %s: %s\n", chip_name, strerror(errno));
        return 1;
    }

    struct gpiod_line *line = gpiod_chip_get_line(chip, line_offset);
    if (!line) {
        fprintf(stderr, "ERR: failed to get line %u: %s\n", line_offset, strerror(errno));
        gpiod_chip_close(chip);
        return 1;
    }

    gpiod_line_release(line);

    /* Open-drain output: 1 = Hi-Z (pull-up), 0 = drive low */
    if (gpiod_line_request_output_flags(line, "dht11_reader",
                                        GPIOD_LINE_REQUEST_FLAG_OPEN_DRAIN, 1) < 0) {
        fprintf(stderr, "ERR: failed to request open-drain output: %s\n", strerror(errno));
        gpiod_chip_close(chip);
        return 1;
    }

    /* Stabilize high */
    gpiod_line_set_value(line, 1);
    busy_wait_us(2000000);

    /* Start signal: low for 20ms */
    gpiod_line_set_value(line, 0);
    busy_wait_us(START_LOW_US);

    /* Release and immediately begin sampling */
    gpiod_line_set_value(line, 1);
    uint64_t release_ts = now_us();

    struct sample samples[MAX_SAMPLES];
    int n_samples = 0;
    uint64_t deadline = release_ts + RESPONSE_TIMEOUT_US + 40 * (80 + 80) + 1000;

    while (n_samples < MAX_SAMPLES && now_us() < deadline) {
        int v = gpiod_line_get_value(line);
        if (v < 0) continue;
        samples[n_samples].ts_us = now_us();
        samples[n_samples].val = v;
        n_samples++;
    }

    gpiod_line_release(line);
    gpiod_chip_close(chip);

    fprintf(stderr, "DEBUG: captured %d samples over %llu us\n",
            n_samples, (unsigned long long)(samples[n_samples - 1].ts_us - samples[0].ts_us));

    /* Find response: first sustained low (>=40us) then high */
    int resp_low_start = -1, resp_low_end = -1, resp_high_end = -1;
    for (int i = 0; i < n_samples - 1; i++) {
        if (resp_low_start < 0 && samples[i].val == 0) {
            resp_low_start = i;
        }
        if (resp_low_start >= 0 && resp_low_end < 0 && samples[i].val == 1) {
            resp_low_end = i;
        }
        if (resp_low_end >= 0 && resp_high_end < 0 && samples[i].val == 0) {
            resp_high_end = i;
            break;
        }
    }

    if (resp_low_start < 0 || resp_low_end < 0 || resp_high_end < 0) {
        fprintf(stderr, "ERR: could not find response pulse\n");
        return 1;
    }

    uint64_t resp_low_dur  = samples[resp_low_end].ts_us - samples[resp_low_start].ts_us;
    uint64_t resp_high_dur = samples[resp_high_end].ts_us - samples[resp_low_end].ts_us;
    fprintf(stderr, "DEBUG: response low=%llu us high=%llu us\n",
            (unsigned long long)resp_low_dur,
            (unsigned long long)resp_high_dur);

    /* Decode 40 bits: each bit starts after a falling edge; high duration decides value */
    uint8_t data[5] = {0};
    int bit_idx = 0;
    int i = resp_high_end;
    while (bit_idx < 40 && i < n_samples - 1) {
        /* find rising edge (start of bit high pulse) */
        while (i < n_samples - 1 && !(samples[i].val == 0 && samples[i + 1].val == 1)) i++;
        if (i >= n_samples - 1) break;
        uint64_t rise_ts = samples[i + 1].ts_us;

        /* find next falling edge (end of bit high pulse) */
        i++;
        while (i < n_samples - 1 && !(samples[i].val == 1 && samples[i + 1].val == 0)) i++;
        if (i >= n_samples - 1) {
            /* last bit: measure until end of capture */
            uint64_t high_dur = samples[n_samples - 1].ts_us - rise_ts;
            int bit = (high_dur > BIT_THRESHOLD_US) ? 1 : 0;
            data[bit_idx / 8] = (data[bit_idx / 8] << 1) | bit;
            fprintf(stderr, "DEBUG: bit %02d high=%llu us -> %d (tail)\n",
                    bit_idx, (unsigned long long)high_dur, bit);
            bit_idx++;
            break;
        }
        uint64_t fall_ts = samples[i + 1].ts_us;
        uint64_t high_dur = fall_ts - rise_ts;

        int bit = (high_dur > BIT_THRESHOLD_US) ? 1 : 0;
        data[bit_idx / 8] = (data[bit_idx / 8] << 1) | bit;
        fprintf(stderr, "DEBUG: bit %02d high=%llu us -> %d\n",
                bit_idx, (unsigned long long)high_dur, bit);
        bit_idx++;
    }

    if (bit_idx != 40) {
        fprintf(stderr, "ERR: only decoded %d bits\n", bit_idx);
        return 1;
    }

    uint8_t checksum = data[0] + data[1] + data[2] + data[3];
    int valid = (checksum == data[4]);

    int humidity = data[0];
    int temperature = data[2];

    printf("{");
    printf("\"valid\":%s,", valid ? "true" : "false");
    printf("\"humidity\":%d,", humidity);
    printf("\"temperature\":%d,", temperature);
    printf("\"raw\":[%u,%u,%u,%u,%u],", data[0], data[1], data[2], data[3], data[4]);
    printf("\"checksum\":%u", checksum);
    printf("}\n");

    return valid ? 0 : 1;
}
