Add firmware nanopb protocol core
This commit is contained in:
375
src/modules/keyboard_proto.c
Normal file
375
src/modules/keyboard_proto.c
Normal file
@@ -0,0 +1,375 @@
|
||||
#include "keyboard_proto.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include <zephyr/sys/byteorder.h>
|
||||
|
||||
#include "display_theme_event.h"
|
||||
#include "function_bitmap_event.h"
|
||||
#include "time_manager.h"
|
||||
#include "time_sync_event.h"
|
||||
|
||||
#include <zephyr/logging/log.h>
|
||||
LOG_MODULE_REGISTER(keyboard_proto, LOG_LEVEL_INF);
|
||||
|
||||
#define KB_PROTO_BODY_TAG_HELLO_REQ 1U
|
||||
#define KB_PROTO_BODY_TAG_HELLO_RSP 2U
|
||||
#define KB_PROTO_BODY_TAG_BITMAP 3U
|
||||
#define KB_PROTO_BODY_TAG_FUNCTION_KEY_EVENT 4U
|
||||
#define KB_PROTO_BODY_TAG_LED_STATE 5U
|
||||
#define KB_PROTO_BODY_TAG_TIME_SYNC 6U
|
||||
#define KB_PROTO_BODY_TAG_THEME_RGB 7U
|
||||
#define KB_PROTO_BODY_TAG_ACK 8U
|
||||
#define KB_PROTO_BODY_TAG_ERROR 9U
|
||||
|
||||
#define KB_PROTO_PACKET_HELLO_REQ 0x01U
|
||||
#define KB_PROTO_PACKET_HELLO_RSP 0x02U
|
||||
#define KB_PROTO_PACKET_BITMAP 0x10U
|
||||
#define KB_PROTO_PACKET_FUNCTION_KEY_EVENT 0x20U
|
||||
#define KB_PROTO_PACKET_LED_STATE 0x21U
|
||||
#define KB_PROTO_PACKET_TIME_SYNC 0x30U
|
||||
#define KB_PROTO_PACKET_THEME_RGB 0x31U
|
||||
#define KB_PROTO_PACKET_ACK 0x7EU
|
||||
#define KB_PROTO_PACKET_ERROR 0x7FU
|
||||
|
||||
#define KB_PROTO_ERROR_UNKNOWN_TYPE 0x01U
|
||||
#define KB_PROTO_ERROR_INVALID_LENGTH 0x02U
|
||||
#define KB_PROTO_ERROR_INVALID_PARAM 0x03U
|
||||
#define KB_PROTO_ERROR_NOT_READY 0x04U
|
||||
|
||||
static uint8_t keyboard_proto_calc_checksum(const uint8_t *data, size_t len)
|
||||
{
|
||||
uint8_t checksum = 0U;
|
||||
|
||||
for (size_t i = 0; i < len; ++i) {
|
||||
checksum ^= data[i];
|
||||
}
|
||||
|
||||
return checksum;
|
||||
}
|
||||
|
||||
static bool keyboard_proto_fill_payload(
|
||||
pb_bytes_array_t *dest,
|
||||
size_t max_size,
|
||||
const uint8_t *src,
|
||||
size_t src_len)
|
||||
{
|
||||
if ((dest == NULL) || (src == NULL) || (src_len > max_size)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
dest->size = src_len;
|
||||
memcpy(dest->bytes, src, src_len);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool keyboard_proto_encode_body(
|
||||
const struct keyboard_cdc_CdcPacketBody *body,
|
||||
uint8_t *buffer,
|
||||
size_t buffer_size,
|
||||
size_t *encoded_size)
|
||||
{
|
||||
pb_ostream_t stream;
|
||||
|
||||
if ((body == NULL) || (buffer == NULL) || (encoded_size == NULL)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
stream = pb_ostream_from_buffer(buffer, buffer_size);
|
||||
if (!pb_encode(&stream, keyboard_cdc_CdcPacketBody_fields, body)) {
|
||||
LOG_WRN("pb_encode body failed: %s", PB_GET_ERROR(&stream));
|
||||
return false;
|
||||
}
|
||||
|
||||
*encoded_size = stream.bytes_written;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool keyboard_proto_decode_body(
|
||||
const uint8_t *buffer,
|
||||
size_t buffer_size,
|
||||
struct keyboard_cdc_CdcPacketBody *body)
|
||||
{
|
||||
pb_istream_t stream;
|
||||
|
||||
if ((buffer == NULL) || (body == NULL)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*body = keyboard_cdc_CdcPacketBody_init_zero;
|
||||
stream = pb_istream_from_buffer(buffer, buffer_size);
|
||||
|
||||
if (!pb_decode(&stream, keyboard_cdc_CdcPacketBody_fields, body)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return stream.bytes_left == 0U;
|
||||
}
|
||||
|
||||
bool keyboard_proto_encode_cdc_frame(
|
||||
uint32_t packet_type,
|
||||
const struct keyboard_cdc_CdcPacketBody *body,
|
||||
uint8_t *buffer,
|
||||
size_t buffer_size,
|
||||
size_t *encoded_size)
|
||||
{
|
||||
struct keyboard_cdc_CdcFrame frame = keyboard_cdc_CdcFrame_init_zero;
|
||||
uint8_t payload[KEYBOARD_PROTO_MAX_BODY_SIZE];
|
||||
size_t payload_size = 0U;
|
||||
pb_ostream_t stream;
|
||||
uint8_t checksum_bytes[4U + KEYBOARD_PROTO_MAX_BODY_SIZE];
|
||||
|
||||
if ((buffer == NULL) || (encoded_size == NULL) || (body == NULL)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!keyboard_proto_encode_body(body, payload, sizeof(payload), &payload_size)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
frame.head1 = 0xAAU;
|
||||
frame.head2 = 0x55U;
|
||||
frame.payload_length = payload_size;
|
||||
frame.type = (keyboard_cdc_CdcPacketType)packet_type;
|
||||
if (!keyboard_proto_fill_payload(&frame.payload, KEYBOARD_PROTO_MAX_BODY_SIZE,
|
||||
payload, payload_size)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
checksum_bytes[0] = (uint8_t)frame.head1;
|
||||
checksum_bytes[1] = (uint8_t)frame.head2;
|
||||
checksum_bytes[2] = (uint8_t)frame.payload_length;
|
||||
checksum_bytes[3] = (uint8_t)packet_type;
|
||||
memcpy(&checksum_bytes[4], payload, payload_size);
|
||||
frame.checksum = keyboard_proto_calc_checksum(checksum_bytes,
|
||||
4U + payload_size);
|
||||
|
||||
stream = pb_ostream_from_buffer(buffer, buffer_size);
|
||||
if (!pb_encode(&stream, keyboard_cdc_CdcFrame_fields, &frame)) {
|
||||
LOG_WRN("pb_encode frame failed: %s", PB_GET_ERROR(&stream));
|
||||
return false;
|
||||
}
|
||||
|
||||
*encoded_size = stream.bytes_written;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool keyboard_proto_validate_frame(const struct keyboard_cdc_CdcFrame *frame)
|
||||
{
|
||||
uint8_t checksum_bytes[4U + KEYBOARD_PROTO_MAX_BODY_SIZE];
|
||||
uint8_t checksum;
|
||||
|
||||
if ((frame == NULL) ||
|
||||
(frame->head1 != 0xAAU) ||
|
||||
(frame->head2 != 0x55U) ||
|
||||
(frame->payload.size != frame->payload_length) ||
|
||||
(frame->payload.size > KEYBOARD_PROTO_MAX_BODY_SIZE)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
checksum_bytes[0] = (uint8_t)frame->head1;
|
||||
checksum_bytes[1] = (uint8_t)frame->head2;
|
||||
checksum_bytes[2] = (uint8_t)frame->payload_length;
|
||||
checksum_bytes[3] = (uint8_t)frame->type;
|
||||
memcpy(&checksum_bytes[4], frame->payload.bytes, frame->payload.size);
|
||||
checksum = keyboard_proto_calc_checksum(checksum_bytes,
|
||||
4U + frame->payload.size);
|
||||
|
||||
return checksum == (uint8_t)frame->checksum;
|
||||
}
|
||||
|
||||
bool keyboard_proto_try_take_cdc_frame(
|
||||
uint8_t *buffer,
|
||||
size_t *buffer_size,
|
||||
struct keyboard_cdc_CdcFrame *frame)
|
||||
{
|
||||
for (size_t candidate_len = 1U; candidate_len <= *buffer_size; ++candidate_len) {
|
||||
struct keyboard_cdc_CdcFrame candidate = keyboard_cdc_CdcFrame_init_zero;
|
||||
pb_istream_t stream = pb_istream_from_buffer(buffer, candidate_len);
|
||||
|
||||
if (!pb_decode(&stream, keyboard_cdc_CdcFrame_fields, &candidate)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((stream.bytes_left != 0U) ||
|
||||
!keyboard_proto_validate_frame(&candidate)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
*frame = candidate;
|
||||
memmove(buffer, buffer + candidate_len, *buffer_size - candidate_len);
|
||||
*buffer_size -= candidate_len;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool keyboard_proto_build_hello_rsp_body(
|
||||
struct keyboard_cdc_CdcPacketBody *body)
|
||||
{
|
||||
*body = keyboard_cdc_CdcPacketBody_init_zero;
|
||||
body->which_body = KB_PROTO_BODY_TAG_HELLO_RSP;
|
||||
body->body.hello_rsp.protocol_version = 1U;
|
||||
body->body.hello_rsp.vendor_id = 0x1209U;
|
||||
body->body.hello_rsp.product_id = 0x0001U;
|
||||
body->body.hello_rsp.firmware_major = 1U;
|
||||
body->body.hello_rsp.firmware_minor = 0U;
|
||||
body->body.hello_rsp.capability_flags = BIT(0) | BIT(1) | BIT(2) | BIT(3);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool keyboard_proto_build_ack_body(
|
||||
uint32_t acked_type,
|
||||
struct keyboard_cdc_CdcPacketBody *body)
|
||||
{
|
||||
*body = keyboard_cdc_CdcPacketBody_init_zero;
|
||||
body->which_body = KB_PROTO_BODY_TAG_ACK;
|
||||
body->body.ack.acked_type = acked_type;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool keyboard_proto_build_error_body(
|
||||
uint32_t error_type,
|
||||
uint32_t error_code,
|
||||
struct keyboard_cdc_CdcPacketBody *body)
|
||||
{
|
||||
*body = keyboard_cdc_CdcPacketBody_init_zero;
|
||||
body->which_body = KB_PROTO_BODY_TAG_ERROR;
|
||||
body->body.error.error_type = error_type;
|
||||
body->body.error.error_code = (keyboard_cdc_ErrorCode)error_code;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool keyboard_proto_build_function_key_event_body(
|
||||
uint16_t usage,
|
||||
bool pressed,
|
||||
struct keyboard_cdc_CdcPacketBody *body)
|
||||
{
|
||||
if (body == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*body = keyboard_cdc_CdcPacketBody_init_zero;
|
||||
body->which_body = KB_PROTO_BODY_TAG_FUNCTION_KEY_EVENT;
|
||||
body->body.function_key_event.usage = usage;
|
||||
body->body.function_key_event.action = pressed ? 1U : 0U;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool keyboard_proto_build_led_state_body(
|
||||
uint8_t led_mask,
|
||||
struct keyboard_cdc_CdcPacketBody *body)
|
||||
{
|
||||
if (body == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*body = keyboard_cdc_CdcPacketBody_init_zero;
|
||||
body->which_body = KB_PROTO_BODY_TAG_LED_STATE;
|
||||
body->body.led_state.led_mask = led_mask;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool keyboard_proto_handle_bitmap(
|
||||
const struct keyboard_cdc_CdcPacketBody *body,
|
||||
keyboard_proto_send_body_fn send_fn,
|
||||
void *user_data)
|
||||
{
|
||||
struct keyboard_cdc_CdcPacketBody response;
|
||||
|
||||
if (body->body.bitmap.usage_bitmap.size != KEYBOARD_PROTO_FUNCTION_BITMAP_SIZE) {
|
||||
keyboard_proto_build_error_body(KB_PROTO_PACKET_BITMAP,
|
||||
KB_PROTO_ERROR_INVALID_LENGTH,
|
||||
&response);
|
||||
return send_fn(&response, user_data);
|
||||
}
|
||||
|
||||
function_bitmap_event_submit(body->body.bitmap.usage_bitmap.bytes,
|
||||
body->body.bitmap.usage_bitmap.size);
|
||||
keyboard_proto_build_ack_body(KB_PROTO_PACKET_BITMAP, &response);
|
||||
return send_fn(&response, user_data);
|
||||
}
|
||||
|
||||
static bool keyboard_proto_handle_time_sync(
|
||||
const struct keyboard_cdc_CdcPacketBody *body,
|
||||
enum keyboard_proto_transport transport,
|
||||
keyboard_proto_send_body_fn send_fn,
|
||||
void *user_data)
|
||||
{
|
||||
struct time_sync_update update;
|
||||
struct keyboard_cdc_CdcPacketBody response;
|
||||
|
||||
if (!time_manager_is_ready()) {
|
||||
keyboard_proto_build_error_body(KB_PROTO_PACKET_TIME_SYNC,
|
||||
KB_PROTO_ERROR_NOT_READY,
|
||||
&response);
|
||||
return send_fn(&response, user_data);
|
||||
}
|
||||
|
||||
if (body->body.time_sync.utc_ms == 0U) {
|
||||
keyboard_proto_build_error_body(KB_PROTO_PACKET_TIME_SYNC,
|
||||
KB_PROTO_ERROR_INVALID_PARAM,
|
||||
&response);
|
||||
return send_fn(&response, user_data);
|
||||
}
|
||||
|
||||
update.utc_ms = body->body.time_sync.utc_ms;
|
||||
update.timezone_min = (int16_t)body->body.time_sync.timezone_min;
|
||||
update.accuracy_ms = body->body.time_sync.accuracy_ms;
|
||||
update.source = (transport == KEYBOARD_PROTO_TRANSPORT_CDC) ?
|
||||
TIME_SYNC_SOURCE_USB : TIME_SYNC_SOURCE_BLE;
|
||||
time_sync_event_submit(&update);
|
||||
|
||||
keyboard_proto_build_ack_body(KB_PROTO_PACKET_TIME_SYNC, &response);
|
||||
return send_fn(&response, user_data);
|
||||
}
|
||||
|
||||
static bool keyboard_proto_handle_theme_rgb(
|
||||
const struct keyboard_cdc_CdcPacketBody *body,
|
||||
keyboard_proto_send_body_fn send_fn,
|
||||
void *user_data)
|
||||
{
|
||||
struct keyboard_cdc_CdcPacketBody response;
|
||||
|
||||
display_theme_event_submit((uint8_t)body->body.theme_rgb.red,
|
||||
(uint8_t)body->body.theme_rgb.green,
|
||||
(uint8_t)body->body.theme_rgb.blue);
|
||||
|
||||
keyboard_proto_build_ack_body(KB_PROTO_PACKET_THEME_RGB, &response);
|
||||
return send_fn(&response, user_data);
|
||||
}
|
||||
|
||||
bool keyboard_proto_handle_host_body(
|
||||
const struct keyboard_cdc_CdcPacketBody *body,
|
||||
enum keyboard_proto_transport transport,
|
||||
keyboard_proto_send_body_fn send_fn,
|
||||
void *user_data)
|
||||
{
|
||||
struct keyboard_cdc_CdcPacketBody response;
|
||||
|
||||
if ((body == NULL) || (send_fn == NULL)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (body->which_body) {
|
||||
case KB_PROTO_BODY_TAG_HELLO_REQ:
|
||||
keyboard_proto_build_hello_rsp_body(&response);
|
||||
return send_fn(&response, user_data);
|
||||
|
||||
case KB_PROTO_BODY_TAG_BITMAP:
|
||||
return keyboard_proto_handle_bitmap(body, send_fn, user_data);
|
||||
|
||||
case KB_PROTO_BODY_TAG_TIME_SYNC:
|
||||
return keyboard_proto_handle_time_sync(body, transport, send_fn, user_data);
|
||||
|
||||
case KB_PROTO_BODY_TAG_THEME_RGB:
|
||||
return keyboard_proto_handle_theme_rgb(body, send_fn, user_data);
|
||||
|
||||
default:
|
||||
keyboard_proto_build_error_body(KB_PROTO_PACKET_ERROR,
|
||||
KB_PROTO_ERROR_UNKNOWN_TYPE,
|
||||
&response);
|
||||
return send_fn(&response, user_data);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user