/*
 * hart_cmd3_decode.c
 *
 * Decodes a HART long-frame Command 3 ("Read Dynamic Variables and PV
 * Current") response.
 *
 * Frame layout assumed (long frame, slave -> master):
 *   Preamble        : N x 0xFF          (not passed to the parser)
 *   Start delimiter : 1 byte  (0x86 for long frame, slave->master)
 *   Address         : 5 bytes
 *   Command         : 1 byte  (0x03)
 *   Byte count      : 1 byte
 *   Response code   : 1 byte
 *   Device status   : 1 byte
 *   Data (24 bytes) : PV current(4) + PV units(1) + PV(4)
 *                     + SV units(1) + SV(4)
 *                     + TV units(1) + TV(4)
 *                     + QV units(1) + QV(4)
 *   Checksum        : 1 byte
 *
 * Build:   gcc -o hart_cmd3_decode hart_cmd3_decode.c
 * Run:     ./hart_cmd3_decode
 */

#include <stdio.h>
#include <stdint.h>
#include <string.h>

/* ------------------------------------------------------------------ */
/* Response code table (HART Universal Command response codes)         */
/* ------------------------------------------------------------------ */
typedef struct {
    uint8_t     code;
    const char *meaning;
} rc_entry_t;

static const rc_entry_t response_codes[] = {
    { 0,  "Success" },
    { 2,  "Invalid selection" },
    { 3,  "Passed parameter too large" },
    { 4,  "Passed parameter too small" },
    { 5,  "Too few data bytes received" },
    { 6,  "Device-specific command error" },
    { 7,  "In write-protect mode" },
    { 8,  "Update failure" },
    { 9,  "Application, device, or communication layer busy" },
    { 15, "Access restricted" },
    { 16, "Device is in a fixed alarm/saturation condition" }, /* device-specific range varies */
    { 32, "Device is busy" },
    { 34, "Command not implemented" },
};

static const char *lookup_response_code(uint8_t rc)
{
    size_t n = sizeof(response_codes) / sizeof(response_codes[0]);
    for (size_t i = 0; i < n; i++) {
        if (response_codes[i].code == rc)
            return response_codes[i].meaning;
    }
    /* Bit 7 set is sometimes used by vendors to flag a command-specific
     * error rather than a communication error -- flag that distinction
     * rather than silently calling it "unknown". */
    if (rc & 0x80)
        return "Vendor/command-specific error (bit 7 set)";
    return "Unrecognized / device-specific response code";
}

/* ------------------------------------------------------------------ */
/* Device status bit meanings (byte immediately following response    */
/* code, per HART Common Table for Field Device Status)                */
/* ------------------------------------------------------------------ */
static void print_device_status(uint8_t status)
{
    printf("Device Status byte: 0x%02X\n", status);
    if (status & 0x01) printf("  [bit0] Field Device Malfunction\n");
    if (status & 0x02) printf("  [bit1] Configuration Changed\n");
    if (status & 0x04) printf("  [bit2] Cold Start\n");
    if (status & 0x08) printf("  [bit3] More Status Available\n");
    if (status & 0x10) printf("  [bit4] Loop Current Fixed\n");
    if (status & 0x20) printf("  [bit5] Loop Current Saturated\n");
    if (status & 0x40) printf("  [bit6] Non-Primary-Variable Out of Limits\n");
    if (status & 0x80) printf("  [bit7] Primary Variable Out of Limits\n");
    if (status == 0)    printf("  (no status bits set)\n");
}

/* ------------------------------------------------------------------ */
/* Minimal unit-code lookup (only the codes verified in this convo)    */
/* ------------------------------------------------------------------ */
static const char *lookup_unit_code(uint8_t code)
{
    switch (code) {
        case 32:  return "degC";
        case 33:  return "degF";
        case 34:  return "degR";
        case 35:  return "K";
        case 36:  return "mV";
        case 37:  return "Ohm";
        case 38:  return "Hz";
        case 39:  return "mA";
        case 57:  return "%";
        case 250: return "Not Used";
        default:
            if (code >= 170 && code <= 219)
                return "(Unit Code Expansion range - meaning depends on Device Variable Classification)";
            return "(unverified / not in local table)";
    }
}

/* ------------------------------------------------------------------ */
/* Big-endian 4-byte -> float, without assuming host byte order        */
/* ------------------------------------------------------------------ */
static float be_bytes_to_float(const uint8_t *p)
{
    uint32_t bits = ((uint32_t)p[0] << 24) |
                    ((uint32_t)p[1] << 16) |
                    ((uint32_t)p[2] << 8)  |
                    ((uint32_t)p[3]);
    float f;
    memcpy(&f, &bits, sizeof(f));
    return f;
}

/* ------------------------------------------------------------------ */
/* Parsed representation of the Command 3 response                     */
/* ------------------------------------------------------------------ */
typedef struct {
    uint8_t start_delimiter;
    uint8_t address[5];
    uint8_t command;
    uint8_t byte_count;
    uint8_t response_code;
    uint8_t device_status;

    float   pv_current;
    uint8_t pv_units;
    float   pv;

    uint8_t sv_units;
    float   sv;

    uint8_t tv_units;
    float   tv;

    uint8_t qv_units;
    float   qv;

    uint8_t checksum;
    int     ok;   /* 1 if the frame was long enough / well-formed */
} hart_cmd3_response_t;

/*
 * Parses a raw byte buffer that starts at the start delimiter
 * (i.e. preamble bytes already stripped) and ends at the checksum.
 * Returns 1 on success, 0 if the buffer is too short.
 */
static int parse_cmd3_response(const uint8_t *buf, size_t len,
                                hart_cmd3_response_t *out)
{
    memset(out, 0, sizeof(*out));

    const size_t MIN_LEN = 1 + 5 + 1 + 1 + 1 + 1 + 24 + 1; /* 35 bytes */
    if (len < MIN_LEN) {
        out->ok = 0;
        return 0;
    }

    size_t i = 0;
    out->start_delimiter = buf[i++];
    memcpy(out->address, &buf[i], 5); i += 5;
    out->command       = buf[i++];
    out->byte_count     = buf[i++];
    out->response_code  = buf[i++];
    out->device_status   = buf[i++];

    out->pv_current = be_bytes_to_float(&buf[i]); i += 4;
    out->pv_units    = buf[i++];
    out->pv          = be_bytes_to_float(&buf[i]); i += 4;

    out->sv_units    = buf[i++];
    out->sv          = be_bytes_to_float(&buf[i]); i += 4;

    out->tv_units    = buf[i++];
    out->tv          = be_bytes_to_float(&buf[i]); i += 4;

    out->qv_units    = buf[i++];
    out->qv          = be_bytes_to_float(&buf[i]); i += 4;

    out->checksum = buf[i++];
    out->ok = 1;
    return 1;
}

static void print_cmd3_response(const hart_cmd3_response_t *r)
{
    if (!r->ok) {
        printf("ERROR: buffer too short for a complete Command 3 response.\n");
        return;
    }

    printf("Start delimiter : 0x%02X\n", r->start_delimiter);
    printf("Address         : %02X %02X %02X %02X %02X\n",
           r->address[0], r->address[1], r->address[2],
           r->address[3], r->address[4]);
    printf("Command         : %u\n", r->command);
    printf("Byte count      : %u\n", r->byte_count);
    printf("Response code   : %u (0x%02X) - %s\n",
           r->response_code, r->response_code,
           lookup_response_code(r->response_code));
    print_device_status(r->device_status);

    printf("\nPV Current : %.4f mA\n", r->pv_current);
    printf("PV         : %.4f  [unit code %u = %s]\n",
           r->pv, r->pv_units, lookup_unit_code(r->pv_units));
    printf("SV         : %.4f  [unit code %u = %s]\n",
           r->sv, r->sv_units, lookup_unit_code(r->sv_units));
    printf("TV         : %.4f  [unit code %u = %s]\n",
           r->tv, r->tv_units, lookup_unit_code(r->tv_units));
    printf("QV         : %.4f  [unit code %u = %s]\n",
           r->qv, r->qv_units, lookup_unit_code(r->qv_units));
    printf("Checksum   : 0x%02X\n", r->checksum);
}

/* ------------------------------------------------------------------ */
/* Optional: compute / verify the XOR checksum                         */
/* (XOR of every byte from the start delimiter through the last data   */
/* byte, i.e. everything except the checksum itself and the preamble)  */
/* ------------------------------------------------------------------ */
static uint8_t compute_checksum(const uint8_t *buf, size_t len_without_checksum)
{
    uint8_t cs = 0;
    for (size_t i = 0; i < len_without_checksum; i++)
        cs ^= buf[i];
    return cs;
}

int main(void)
{
    /* Example frame (preamble stripped), matching the worked example
     * used earlier in this conversation, with a valid checksum appended. */
    uint8_t frame[] = {
        0x86,                               /* start delimiter        */
        0x11, 0xA3, 0xF8, 0x2D, 0x14,        /* address                */
        0x03,                               /* command                */
        0x1A,                               /* byte count (26)        */
        0x00,                               /* response code          */
        0x40,                               /* device status          */
        0x41, 0x25, 0x4B, 0xC7,             /* PV current             */
        0x3B,                               /* PV units               */
        0x40, 0xB1, 0x44, 0xBC,             /* PV                     */
        0x20,                               /* SV units               */
        0x41, 0xB3, 0x99, 0x9A,             /* SV                     */
        0x24,                               /* TV units               */
        0x42, 0xAA, 0xCC, 0xCD,             /* TV                     */
        0xAA,                               /* QV units               */
        0x00, 0x00, 0x00, 0x00,             /* QV                     */
        0x00                                /* placeholder checksum   */
    };

    size_t len = sizeof(frame);

    /* Fill in a valid checksum over everything except the last byte */
    frame[len - 1] = compute_checksum(frame, len - 1);

    hart_cmd3_response_t resp;
    parse_cmd3_response(frame, len, &resp);
    print_cmd3_response(&resp);

    return 0;
}
