/*
 * hart_request.c
 *
 * Builds a HART data-link request frame and (optionally) sends it out
 * a serial port to a HART modem.
 *
 * Example here builds Command 3 - "Read Dynamic Variables and Loop
 * Current", which returns Primary, Secondary, Tertiary, and
 * Quaternary variables in one response. It needs no request data.
 *
 * If you need to request a *specific* variable (e.g. only the
 * secondary variable) rather than all four, use Command 9 instead
 * and pass the variable-code bytes as the request data -- see the
 * build_command9_request() helper below.
 *
 * This only builds/sends the request. Parsing the response is a
 * separate step (see hart_parse.py from earlier in this conversation
 * for the response side).
 *
 * Compile: gcc -o hart_request hart_request.c
 */

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

#define PREAMBLE_LEN     5      /* 5-20 bytes of 0xFF; 5 is typical */
#define MAX_FRAME_LEN     64

/* Delimiter byte values */
#define DELIM_SHORT_STX  0x02   /* short-frame, master-to-slave request */
#define DELIM_LONG_STX   0x82   /* long-frame,  master-to-slave request */

/*
 * Build a generic HART request frame.
 *
 * short_addr:   if non-negative (0-15), use short-frame addressing.
 * long_addr:    5-byte unique address, used if short_addr < 0.
 * command:      HART command number.
 * data:         command's request data bytes (NULL if none).
 * data_len:     length of data.
 * out:          output buffer, must be at least MAX_FRAME_LEN bytes.
 *
 * Returns the total frame length, or -1 on error.
 */
int build_hart_request(int short_addr, const uint8_t long_addr[5],
                        uint8_t command, const uint8_t *data, uint8_t data_len,
                        uint8_t *out)
{
    if (data_len > 24 || out == NULL) {
        return -1;
    }

    int i = 0;

    /* Preamble */
    for (int p = 0; p < PREAMBLE_LEN; p++) {
        out[i++] = 0xFF;
    }

    int checksum_start = i;

    /* Delimiter + address */
    if (short_addr >= 0) {
        if (short_addr > 15) return -1;
        out[i++] = DELIM_SHORT_STX;
        /* bit7 = primary master (1), bit6 = burst mode (0),
         * bits5-4 reserved (0), bits3-0 = polling address */
        out[i++] = 0x80 | (short_addr & 0x0F);
    } else {
        out[i++] = DELIM_LONG_STX;
        memcpy(&out[i], long_addr, 5);
        i += 5;
    }

    /* Command */
    out[i++] = command;

    /* Byte count */
    out[i++] = data_len;

    /* Data */
    if (data_len > 0) {
        memcpy(&out[i], data, data_len);
        i += data_len;
    }

    /* Checksum: XOR of delimiter through end of data */
    uint8_t checksum = 0;
    for (int j = checksum_start; j < i; j++) {
        checksum ^= out[j];
    }
    out[i++] = checksum;

    return i;
}

/* Command 3: Read Dynamic Variables and Loop Current. No request data. */
int build_command3_request(int short_addr, const uint8_t long_addr[5], uint8_t *out)
{
    return build_hart_request(short_addr, long_addr, 3, NULL, 0, out);
}

/*
 * Command 9: Read Device Variables with Status. Request data is a
 * list of "slot codes" (0-3 = PV/SV/TV/QV in the classic mapping,
 * though HART 7 uses fuller device-variable codes -- check the
 * device manual). This example asks for just the secondary variable
 * (slot code 1).
 */
int build_command9_request_sv_only(int short_addr, const uint8_t long_addr[5], uint8_t *out)
{
    uint8_t data[1] = { 0x01 };  /* slot 1 = secondary variable */
    return build_hart_request(short_addr, long_addr, 9, data, 1, out);
}

static void print_frame(const uint8_t *buf, int len)
{
    for (int i = 0; i < len; i++) {
        printf("%02X ", buf[i]);
    }
    printf("\n");
}

int main(void)
{
    uint8_t frame[MAX_FRAME_LEN];
    int len;

    /* Example 1: short-frame address 0, Command 3 (all dynamic vars) */
    len = build_command3_request(0, NULL, frame);
    printf("Command 3 request (short addr 0): ");
    print_frame(frame, len);

    /* Example 2: long-frame address, Command 9 requesting only SV */
    uint8_t long_addr[5] = { 0xA6, 0x55, 0x69, 0x52, 0xE4 };
    len = build_command9_request_sv_only(-1, long_addr, frame);
    printf("Command 9 request (SV only, long addr): ");
    print_frame(frame, len);

    /*
     * To actually transmit `frame` you'd write() it to a serial port
     * opened against your HART modem, after configuring the port for
     * 1200 baud, 8 data bits, odd parity, 1 stop bit (the physical
     * layer HART runs over). That part is platform/modem specific,
     * so it's not included here -- happy to add it for your setup
     * (e.g. a specific USB HART modem on Linux) if useful.
     */

    return 0;
}
