/* * @file protocol.h * @brief Serial protocol definitions, command structures and dispatch table. * * Frame format: [CMD : 2B][LEN : 2B][PAYLOAD : LEN B][CRC16 : 2B] * - CMD : 16-bit little-endian command identifier. * - LEN : 16-bit little-endian payload length, excluding CRC. * - PAYLOAD : LEN bytes, command-specific. * - CRC16 : CRC16-XMODEM (poly 0x1021, init 0x0000) over CMD+LEN+PAYLOAD. * * Response identifiers: * - 0x414B "AK" : command accepted and executed. * - 0x4E4B "NK" : valid command but cannot execute (bad params, wrong state). * - 0x4552 "ER" : malformed frame (bad CRC, bad length). * * Created: 07.05.2026 * Author: ThePetrovich * * Copyright YKSA - Sakha Aerospace Systems, LLC. * See the LICENSE file for details. * * SPDX-License-Identifier: BSD-3-Clause */ #ifndef PROTOCOL_H #define PROTOCOL_H #include "config.h" #include "lsense.h" #include #define PROTOCOL_RESP_ACK 0x414B /* "AK" */ #define PROTOCOL_RESP_NAK 0x4E4B /* "NK" */ #define PROTOCOL_RESP_ERR 0x4552 /* "ER" */ /** * @brief Compute CRC16-XMODEM (poly 0x1021, init 0x0000) over a buffer. * @param data Pointer to input bytes. * @param len Number of bytes to process. * @return Final CRC value. */ uint16_t protocol_crc16_xmodem(const uint8_t *data, uint16_t len); /** * @brief Incrementally update a running CRC16-XMODEM with additional bytes. * Pass @c crc=0 for the first call, then chain the result through * further calls. This is the single CRC primitive used by the protocol. * @param crc Current CRC value. * @param data Pointer to input bytes. * @param len Number of bytes to process. * @return Updated CRC value. */ uint16_t protocol_crc16_update(uint16_t crc, const uint8_t *data, uint16_t len); typedef struct protocol_header_t { uint16_t cmd; uint16_t length; } __attribute__((packed)) protocol_header_t; /** * @brief Send a complete framed message: header + payload + CRC. * @param cmd Command identifier. * @param payload Optional payload pointer (may be NULL when @p length is 0). * @param length Payload length in bytes. */ void protocol_send_message(uint16_t cmd, const void *payload, uint16_t length); /** * @brief Send a framed message whose payload is split across two fragments. * CRC is computed across header + frag1 + frag2 in order. */ void protocol_send_message_v2(uint16_t cmd, const void *frag1, uint16_t len1, const void *frag2, uint16_t len2); /** @brief Send an ACK response (cmd = PROTOCOL_RESP_ACK, no payload). */ void protocol_send_ack(void); /** @brief Send a NAK response (cmd = PROTOCOL_RESP_NAK, no payload). */ void protocol_send_nak(void); /** @brief Send an ERR response (cmd = PROTOCOL_RESP_ERR, no payload). */ void protocol_send_error(void); /* * X-macro command dispatch table. * * X(EnumName, CmdId, HandlerFn) is the single source of truth for all * supported commands. It is expanded to: * - an enumeration of command IDs (e.g. CMD_FIRMWARE_VERSION), * - prototypes for every handler function, * - the dispatch switch in sbc_fw.ino. * The same X-macro can be re-included on the host/master side to generate * matching send helpers. */ #define X_PROTOCOL_handlerS \ X(CMD_FIRMWARE_VERSION, 0x7600, protocol_version_handler) \ X(CMD_LIGHT_SENSOR_PRESENCE, 0x6C00, protocol_light_sensor_presence_handler) \ X(CMD_READ_LIGHT_SENSORS, 0x7200, protocol_read_light_sensors_handler) \ X(CMD_GET_CONFIGURATION, 0x6D00, protocol_get_configuration_handler) \ X(CMD_SET_CONFIGURATION, 0x7D00, protocol_set_configuration_handler) \ X(CMD_SET_POTENTIOMETERS, 0x7D01, protocol_set_potentiometers_handler) \ X(CMD_SET_CALIBRATION, 0x7D02, protocol_set_calibration_handler) \ X(CMD_SET_FLAGS, 0x7D03, protocol_set_flags_handler) \ X(CMD_SET_TEMP_DRIFT_COMPENSATION, 0x7D04, protocol_set_temp_drift_compensation_handler) \ X(CMD_SET_DRIFT_COMPENSATION_ENABLE, 0x7D05, protocol_set_drift_compensation_enable_handler) \ X(CMD_GET_TELEMETRY, 0x7400, protocol_get_telemetry_handler) \ X(CMD_FLUSH_COUNTERS, 0x7401, protocol_flush_counters_handler) \ X(CMD_FLUSH_SPECTRUM, 0x7402, protocol_flush_spectrum_handler) \ X(CMD_RSENSE_ENABLE, 0x7500, protocol_rsense_enable_handler) \ X(CMD_RSENSE_DISABLE, 0x7501, protocol_rsense_disable_handler) \ X(CMD_SPECTRUM_FREEZE, 0x7502, protocol_spectrum_freeze_handler) \ X(CMD_SPECTRUM_UNFREEZE, 0x7503, protocol_spectrum_unfreeze_handler) \ X(CMD_GET_COUNTS, 0x7504, protocol_get_counts_handler) \ X(CMD_READ_SPECTRUM_DATA, 0x7505, protocol_read_spectrum_chunk_handler) /** @brief Symbolic command IDs generated from #X_PROTOCOL_handlerS. */ enum protocol_cmd_id { #define X(Enum, Cmd_id, Handler) Enum = Cmd_id, X_PROTOCOL_handlerS #undef X }; /* Command handler prototypes generated from X_PROTOCOL_handlerS. */ #define X(Enum, Cmd_id, Handler) void Handler(void *payload, uint16_t length); X_PROTOCOL_handlerS #undef X /** @brief Response payload for #CMD_FIRMWARE_VERSION. */ typedef struct protocol_firmware_version_response_t { uint8_t major; uint8_t minor; } __attribute__((packed)) protocol_firmware_version_response_t; /* Light sensor protocol payloads reuse the lsense module types directly * so handlers can populate them via a single call without field copies. */ /** @brief Response payload for #CMD_LIGHT_SENSOR_PRESENCE — alias of #lsense_presence_t. */ typedef lsense_presence_t protocol_light_sensor_presence_response_t; /** * @brief Command payload for #CMD_READ_LIGHT_SENSORS. * * channel: 0=red 1=green 2=blue 3=IR 4=green2 * 5=visible (R+G+B+G2) 6=all (R+G+B+G2+IR) */ typedef struct protocol_read_light_sensors_handler_t { uint8_t channel; } __attribute__((packed)) protocol_read_light_sensors_handler_t; /** @brief Response payload for #CMD_READ_LIGHT_SENSORS — alias of #lsense_channel_values_t. */ typedef lsense_channel_values_t protocol_read_light_sensors_response_t; /* Configuration commands reuse the persisted-config types from config.h * directly. The on-the-wire layout is the corresponding struct, so handlers * can use a single struct assignment in place of field-by-field copies. */ /** @brief Response payload for #CMD_GET_CONFIGURATION — alias of #config_t. */ typedef config_t protocol_get_configuration_response_t; /** @brief Command payload for #CMD_SET_CONFIGURATION — alias of #config_t. */ typedef config_t protocol_set_configuration_handler_t; /** @brief Command payload for #CMD_SET_POTENTIOMETERS — alias of #potentiometers_t. */ typedef potentiometers_t protocol_set_potentiometers_handler_t; /** @brief Command payload for #CMD_SET_CALIBRATION — alias of #calibration_t. */ typedef calibration_t protocol_set_calibration_handler_t; /** @brief Command payload for #CMD_SET_FLAGS — alias of #config_flags_t. */ typedef config_flags_t protocol_set_flags_handler_t; /** @brief Command payload for #CMD_SET_TEMP_DRIFT_COMPENSATION — alias of #temp_drift_compensation_t. */ typedef temp_drift_compensation_t protocol_set_temp_drift_compensation_handler_t; /** @brief Command payload for #CMD_SET_DRIFT_COMPENSATION_ENABLE. */ typedef struct protocol_set_drift_compensation_enable_handler_t { uint8_t enable; /* 1 = enable, 0 = disable */ } __attribute__((packed)) protocol_set_drift_compensation_enable_handler_t; /** @brief Response payload for #CMD_GET_TELEMETRY. */ typedef struct protocol_get_telemetry_response_t { uint16_t hv_temp_c; /**< 0.1 °C — HV_TEMP_PIN, +28V supply sensor. */ uint16_t amp_temp_c; /**< 0.1 °C — AMP_TEMP_PIN, amplifier sensor. */ uint16_t sipm_temp_c; /**< 0.1 °C — DET_TEMP_PIN, SiPM carrier sensor. */ uint16_t mcu_temp_c; /**< Kelvin — MCU internal temperature sensor. */ uint16_t vbias_mv; potentiometers_t pots; uint16_t cps; /**< counts per second = CPM / 60. */ uint32_t cp10s; /**< counts in the last 10 s window. */ uint32_t total_counts; uint8_t flags; /**< bit 0: power on ok, bit 1: sensing enabled, bit 2: drift compensation enabled, bit 3: reserved. */ } __attribute__((packed)) protocol_get_telemetry_response_t; /** @brief Response payload for #CMD_GET_COUNTS. */ typedef struct protocol_get_counts_response_t { uint32_t counts; } __attribute__((packed)) protocol_get_counts_response_t; /** @brief Command payload for #CMD_READ_SPECTRUM_DATA. */ typedef struct protocol_read_spectrum_chunk_handler_t { uint16_t offset; /* byte offset into spectrum data */ uint16_t length; /* number of bytes to read */ } __attribute__((packed)) protocol_read_spectrum_chunk_handler_t; /** * @brief Response header for #CMD_READ_SPECTRUM_DATA. * Followed on the wire by @c length bytes of raw spectrum data. */ typedef struct protocol_read_spectrum_chunk_response_t { uint16_t offset; uint16_t length; } __attribute__((packed)) protocol_read_spectrum_chunk_response_t; #endif /* PROTOCOL_H */