* UART and BLE/GATT transports for a protobuf encoded RPC request/response protocol. * Custom framing protocol is used to frame a give message. * Requests/responses are divided into major "subsystems" which handle requests and create response messages. * Notification support, including mapping local events to RPC notifications by a given subsystem. * Meta responses for "no response" and "unlock needed". * Initial basic lock state support in a new core section, and allow specifying if a given RPC callback requires unlocked state or not. * Add behavior subsystem with full metadata support and examples of using callback to serialize a repeated field without extra stack space needed. Co-authored-by: Cem Aksoylar <caksoylar@users.noreply.github.com>
170 lines
4.8 KiB
C
170 lines
4.8 KiB
C
/*
|
|
* Copyright (c) 2024 The ZMK Contributors
|
|
*
|
|
* SPDX-License-Identifier: MIT
|
|
*/
|
|
|
|
#include <zephyr/init.h>
|
|
#include <zephyr/kernel.h>
|
|
#include <zephyr/device.h>
|
|
#include <zephyr/drivers/uart.h>
|
|
#include <zephyr/sys/ring_buffer.h>
|
|
|
|
#include <zephyr/logging/log.h>
|
|
#include <zmk/studio/rpc.h>
|
|
|
|
LOG_MODULE_DECLARE(zmk_studio, CONFIG_ZMK_STUDIO_LOG_LEVEL);
|
|
|
|
/* change this to any other UART peripheral if desired */
|
|
#define UART_DEVICE_NODE DT_CHOSEN(zmk_studio_rpc_uart)
|
|
|
|
static const struct device *const uart_dev = DEVICE_DT_GET(UART_DEVICE_NODE);
|
|
|
|
static void tx_notify(struct ring_buf *tx_ring_buf, size_t written, bool msg_done,
|
|
void *user_data) {
|
|
if (msg_done || (ring_buf_size_get(tx_ring_buf) > (ring_buf_capacity_get(tx_ring_buf) / 2))) {
|
|
#if IS_ENABLED(CONFIG_UART_INTERRUPT_DRIVEN)
|
|
uart_irq_tx_enable(uart_dev);
|
|
#else
|
|
struct ring_buf *tx_buf = zmk_rpc_get_tx_buf();
|
|
uint8_t *buf;
|
|
uint32_t claim_len;
|
|
while ((claim_len = ring_buf_get_claim(tx_buf, &buf, tx_buf->size)) > 0) {
|
|
for (int i = 0; i < claim_len; i++) {
|
|
uart_poll_out(uart_dev, buf[i]);
|
|
}
|
|
|
|
ring_buf_get_finish(tx_buf, claim_len);
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
#if !IS_ENABLED(CONFIG_UART_INTERRUPT_DRIVEN)
|
|
|
|
static void uart_rx_main(void) {
|
|
for (;;) {
|
|
uint8_t *buf;
|
|
struct ring_buf *ring_buf = zmk_rpc_get_rx_buf();
|
|
uint32_t claim_len = ring_buf_put_claim(ring_buf, &buf, 1);
|
|
|
|
if (claim_len < 1) {
|
|
LOG_WRN("NO CLAIM ABLE TO BE HAD");
|
|
k_sleep(K_MSEC(1));
|
|
continue;
|
|
}
|
|
|
|
if (uart_poll_in(uart_dev, buf) < 0) {
|
|
ring_buf_put_finish(ring_buf, 0);
|
|
k_sleep(K_MSEC(1));
|
|
} else {
|
|
ring_buf_put_finish(ring_buf, 1);
|
|
zmk_rpc_rx_notify();
|
|
}
|
|
}
|
|
}
|
|
|
|
K_THREAD_DEFINE(uart_transport_read_thread, CONFIG_ZMK_STUDIO_TRANSPORT_UART_RX_STACK_SIZE,
|
|
uart_rx_main, NULL, NULL, NULL, K_LOWEST_APPLICATION_THREAD_PRIO, 0, 0);
|
|
|
|
#endif
|
|
|
|
static int start_rx() {
|
|
#if IS_ENABLED(CONFIG_UART_INTERRUPT_DRIVEN)
|
|
uart_irq_rx_enable(uart_dev);
|
|
#else
|
|
k_thread_resume(uart_transport_read_thread);
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
static int stop_rx(void) {
|
|
#if IS_ENABLED(CONFIG_UART_INTERRUPT_DRIVEN)
|
|
uart_irq_rx_disable(uart_dev);
|
|
#else
|
|
k_thread_suspend(uart_transport_read_thread);
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
ZMK_RPC_TRANSPORT(uart, ZMK_TRANSPORT_USB, start_rx, stop_rx, NULL, tx_notify);
|
|
|
|
#if IS_ENABLED(CONFIG_UART_INTERRUPT_DRIVEN)
|
|
|
|
/*
|
|
* Read characters from UART until line end is detected. Afterwards push the
|
|
* data to the message queue.
|
|
*/
|
|
static void serial_cb(const struct device *dev, void *user_data) {
|
|
if (!uart_irq_update(uart_dev)) {
|
|
return;
|
|
}
|
|
|
|
if (uart_irq_rx_ready(uart_dev)) {
|
|
/* read until FIFO empty */
|
|
uint32_t last_read = 0, len = 0;
|
|
struct ring_buf *buf = zmk_rpc_get_rx_buf();
|
|
do {
|
|
uint8_t *buffer;
|
|
len = ring_buf_put_claim(buf, &buffer, buf->size);
|
|
if (len > 0) {
|
|
last_read = uart_fifo_read(uart_dev, buffer, len);
|
|
|
|
ring_buf_put_finish(buf, last_read);
|
|
} else {
|
|
LOG_ERR("Dropping incoming RPC byte, insufficient room in the RX buffer. Bump "
|
|
"CONFIG_ZMK_STUDIO_RPC_RX_BUF_SIZE.");
|
|
uint8_t dummy;
|
|
last_read = uart_fifo_read(uart_dev, &dummy, 1);
|
|
}
|
|
} while (last_read && last_read == len);
|
|
|
|
zmk_rpc_rx_notify();
|
|
}
|
|
|
|
if (uart_irq_tx_ready(uart_dev)) {
|
|
struct ring_buf *tx_buf = zmk_rpc_get_tx_buf();
|
|
uint32_t len;
|
|
while ((len = ring_buf_size_get(tx_buf)) > 0) {
|
|
uint8_t *buf;
|
|
uint32_t claim_len = ring_buf_get_claim(tx_buf, &buf, tx_buf->size);
|
|
|
|
if (claim_len == 0) {
|
|
continue;
|
|
}
|
|
|
|
int sent = uart_fifo_fill(uart_dev, buf, claim_len);
|
|
|
|
ring_buf_get_finish(tx_buf, MAX(sent, 0));
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
static int uart_rpc_interface_init(void) {
|
|
if (!device_is_ready(uart_dev)) {
|
|
LOG_ERR("UART device not found!");
|
|
return -ENODEV;
|
|
}
|
|
|
|
#if IS_ENABLED(CONFIG_UART_INTERRUPT_DRIVEN)
|
|
/* configure interrupt and callback to receive data */
|
|
int ret = uart_irq_callback_user_data_set(uart_dev, serial_cb, NULL);
|
|
|
|
if (ret < 0) {
|
|
if (ret == -ENOTSUP) {
|
|
printk("Interrupt-driven UART API support not enabled\n");
|
|
} else if (ret == -ENOSYS) {
|
|
printk("UART device does not support interrupt-driven API\n");
|
|
} else {
|
|
printk("Error setting UART callback: %d\n", ret);
|
|
}
|
|
return ret;
|
|
}
|
|
#endif // IS_ENABLED(CONFIG_UART_INTERRUPT_DRIVEN)
|
|
|
|
return 0;
|
|
}
|
|
|
|
SYS_INIT(uart_rpc_interface_init, POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT);
|