From 5020e5bd282dd7e3a9086722ca0a1a6fa8e672b0 Mon Sep 17 00:00:00 2001 From: Totoo Date: Fri, 8 Nov 2024 11:46:27 +0100 Subject: [PATCH] I2cshell (#2348) I2C shell implementation. --- firmware/application/CMakeLists.txt | 1 + .../apps/ui_external_module_view.cpp | 2 +- firmware/application/i2c_device_to_host.c | 110 ++++++++++++++++++ firmware/application/i2c_device_to_host.h | 58 +++++++++ firmware/application/main.cpp | 2 + firmware/application/usb_serial_shell.cpp | 14 +++ firmware/application/usb_serial_shell.hpp | 8 ++ firmware/common/i2cdev_ppmod.cpp | 69 +++++++++-- firmware/common/i2cdev_ppmod.hpp | 10 ++ firmware/common/i2cdev_ppmod_helper.hpp | 17 +-- firmware/common/i2cdevmanager.cpp | 9 ++ firmware/common/i2cdevmanager.hpp | 4 + firmware/common/i2cdevmanager_c_api.h | 13 +++ 13 files changed, 298 insertions(+), 19 deletions(-) create mode 100644 firmware/application/i2c_device_to_host.c create mode 100644 firmware/application/i2c_device_to_host.h create mode 100644 firmware/common/i2cdevmanager_c_api.h diff --git a/firmware/application/CMakeLists.txt b/firmware/application/CMakeLists.txt index daf96900..74076af7 100644 --- a/firmware/application/CMakeLists.txt +++ b/firmware/application/CMakeLists.txt @@ -117,6 +117,7 @@ set(CSRC usb_serial_descriptor.c usb_serial_endpoints.c usb_serial_device_to_host.c + i2c_device_to_host.c ${HACKRF_PATH}/firmware/common/usb.c ${HACKRF_PATH}/firmware/common/usb_queue.c ${HACKRF_PATH}/firmware/hackrf_usb/usb_device.c diff --git a/firmware/application/apps/ui_external_module_view.cpp b/firmware/application/apps/ui_external_module_view.cpp index 872f0e49..5e376981 100644 --- a/firmware/application/apps/ui_external_module_view.cpp +++ b/firmware/application/apps/ui_external_module_view.cpp @@ -80,7 +80,7 @@ void ExternalModuleView::on_tick_second() { continue; } - std::string btnText = (std::string) "App " + std::to_string(device_info->application_count) + ": " + (const char*)appInfo->app_name; + std::string btnText = (std::string) "App " + std::to_string(i + 1) + ": " + (const char*)appInfo->app_name; switch (appInfo->menu_location) { case app_location_t::UTILITIES: diff --git a/firmware/application/i2c_device_to_host.c b/firmware/application/i2c_device_to_host.c new file mode 100644 index 00000000..b243b9a5 --- /dev/null +++ b/firmware/application/i2c_device_to_host.c @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2023 Bernd Herzog + * Copyright (C) 2024 HTotoo + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#include "i2c_device_to_host.h" +#include "i2cdevmanager_c_api.h" +#include + +I2CShellDriver I2CD1; + +// pp to i2c data tx +static void onotifyi2c(GenericQueue* qp) { + I2CShellDriver* sdp = chQGetLink(qp); + uint8_t buff[I2CSHELL_BUFFERS_SIZE]; + int n = chOQGetFullI(&sdp->oqueue); + if (n > I2CSHELL_BUFFERS_SIZE) n = I2CSHELL_BUFFERS_SIZE; // don't overflow + if (n > 0) { + for (int i = 0; i < n; i++) { + buff[i] = chOQGetI(&sdp->oqueue); + } + int ret; + chSysUnlock(); + do { + ret = oNofityI2cFromShell(&buff[0], n); // i2c_write + if (ret == -1) + chThdSleepMilliseconds(1); + } while (ret == -1); + chSysLock(); + } +} + +static size_t write(void* ip, const uint8_t* bp, size_t n) { + return chOQWriteTimeout(&((I2CShellDriver*)ip)->oqueue, bp, + n, TIME_INFINITE); +} + +static size_t read(void* ip, uint8_t* bp, size_t n) { + return chIQReadTimeout(&((I2CShellDriver*)ip)->iqueue, bp, + n, TIME_INFINITE); +} + +static msg_t put(void* ip, uint8_t b) { + return chOQPutTimeout(&((I2CShellDriver*)ip)->oqueue, b, TIME_INFINITE); +} + +static msg_t get(void* ip) { + return chIQGetTimeout(&((I2CShellDriver*)ip)->iqueue, TIME_INFINITE); +} + +static msg_t putt(void* ip, uint8_t b, systime_t timeout) { + return chOQPutTimeout(&((I2CShellDriver*)ip)->oqueue, b, timeout); +} + +static msg_t gett(void* ip, systime_t timeout) { + return chIQGetTimeout(&((I2CShellDriver*)ip)->iqueue, timeout); +} + +static size_t writet(void* ip, const uint8_t* bp, size_t n, systime_t time) { + return chOQWriteTimeout(&((I2CShellDriver*)ip)->oqueue, bp, n, time); +} + +static size_t readt(void* ip, uint8_t* bp, size_t n, systime_t time) { + return chIQReadTimeout(&((I2CShellDriver*)ip)->iqueue, bp, n, time); +} + +static const struct I2CShellDriverVMT vmt = { + write, read, put, get, + putt, gett, writet, readt}; + +void init_i2c_shell_driver(I2CShellDriver* sdp) { + sdp->vmt = &vmt; + chIQInit(&sdp->iqueue, sdp->ib, I2CSHELL_BUFFERS_SIZE, NULL, sdp); + chOQInit(&sdp->oqueue, sdp->ob, I2CSHELL_BUFFERS_SIZE, onotifyi2c, sdp); +} + +// i2c->pp data rx +void complete_i2chost_to_device_transfer(uint8_t* data, size_t length) { + chSysLock(); + for (unsigned int i = 0; i < length; i++) { + msg_t ret; + do { + ret = chIQPutI(&I2CD1.iqueue, data[i]); + if (ret == Q_FULL) { + chSysUnlock(); + chThdSleepMilliseconds(1); // wait for shell thread when buffer is full + chSysLock(); + } + + } while (ret == Q_FULL); + } + chSysUnlock(); +} diff --git a/firmware/application/i2c_device_to_host.h b/firmware/application/i2c_device_to_host.h new file mode 100644 index 00000000..61827106 --- /dev/null +++ b/firmware/application/i2c_device_to_host.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2023 Bernd Herzog + * Copyright (C) 2024 HTotoo + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __I2C_DEVICE_TO_HOST_H +#define __I2C_DEVICE_TO_HOST_H + +#include "ch.h" +#include "hal.h" + +#ifndef I2CSHELL_BUFFERS_SIZE +#define I2CSHELL_BUFFERS_SIZE 64 +#endif + +struct I2CShellDriverVMT { + _base_asynchronous_channel_methods +}; + +struct I2CShellDriver { + /** @brief Virtual Methods Table.*/ + const struct I2CShellDriverVMT* vmt; + InputQueue iqueue; /* Output queue.*/ + OutputQueue oqueue; /* Input circular buffer.*/ + uint8_t ib[I2CSHELL_BUFFERS_SIZE]; /* Output circular buffer.*/ + uint8_t ob[I2CSHELL_BUFFERS_SIZE]; +}; + +typedef struct I2CShellDriver I2CShellDriver; + +extern I2CShellDriver I2CD1; + +#ifdef __cplusplus +extern "C" { +#endif +void init_i2c_shell_driver(I2CShellDriver* sdp); +#ifdef __cplusplus +} +#endif + +#endif diff --git a/firmware/application/main.cpp b/firmware/application/main.cpp index a595bae1..c1ca5b5c 100755 --- a/firmware/application/main.cpp +++ b/firmware/application/main.cpp @@ -139,6 +139,7 @@ Continuous (Fox-oring) #include "sd_card.hpp" #include +#include "i2cdevmanager.hpp" #include "rffc507x.hpp" /* c/m, avoiding initial short ON Ant_DC_Bias pulse, from cold reset */ rffc507x::RFFC507x first_if; @@ -159,6 +160,7 @@ static void event_loop() { event_dispatcher.set_display_sleep(true); }}; portapack::setEventDispatcherToUSBSerial(&event_dispatcher); + i2cdev::I2CDevManager::setEventDispatcher(&event_dispatcher); system_view.get_navigation_view()->handle_autostart(); event_dispatcher.run(); } diff --git a/firmware/application/usb_serial_shell.cpp b/firmware/application/usb_serial_shell.cpp index c99e3e30..5dcd010c 100644 --- a/firmware/application/usb_serial_shell.cpp +++ b/firmware/application/usb_serial_shell.cpp @@ -38,6 +38,7 @@ #include "performance_counter.hpp" #include "usb_serial_device_to_host.h" +#include "i2c_device_to_host.h" #include "chprintf.h" #include "chqueues.h" #include "ui_external_items_menu_loader.hpp" @@ -59,6 +60,7 @@ #define palOutputPad(port, pad) (LPC_GPIO->DIR[(port)] |= 1 << (pad)) static EventDispatcher* _eventDispatcherInstance = NULL; +static bool shell_i2c_created = false; static EventDispatcher* getEventDispatcherInstance() { return _eventDispatcherInstance; } @@ -1222,7 +1224,19 @@ static const ShellConfig shell_cfg1 = { (BaseSequentialStream*)&SUSBD1, commands}; +static const ShellConfig shell_cfg2 = { + (BaseSequentialStream*)&I2CD1, + commands}; + void create_shell(EventDispatcher* evtd) { _eventDispatcherInstance = evtd; shellCreate(&shell_cfg1, SHELL_WA_SIZE, NORMALPRIO + 10); } + +extern "C" void create_shell_i2c(EventDispatcher* evtd) { + if (shell_i2c_created) return; + shell_i2c_created = true; + init_i2c_shell_driver(&I2CD1); + _eventDispatcherInstance = evtd; + shellCreate(&shell_cfg2, SHELL_WA_SIZE, NORMALPRIO + 10); +} diff --git a/firmware/application/usb_serial_shell.hpp b/firmware/application/usb_serial_shell.hpp index 6a049ab2..fbb406c3 100644 --- a/firmware/application/usb_serial_shell.hpp +++ b/firmware/application/usb_serial_shell.hpp @@ -31,4 +31,12 @@ class EventDispatcher; void create_shell(EventDispatcher* evtd); +#ifdef __cplusplus +extern "C" { +#endif +void create_shell_i2c(EventDispatcher* evtd); +#ifdef __cplusplus +} +#endif + #endif diff --git a/firmware/common/i2cdev_ppmod.cpp b/firmware/common/i2cdev_ppmod.cpp index 85000ef7..9699b5fa 100644 --- a/firmware/common/i2cdev_ppmod.cpp +++ b/firmware/common/i2cdev_ppmod.cpp @@ -23,47 +23,86 @@ #include "portapack.hpp" #include +#define SENSORUPDATETIME 10 + +extern "C" { +void complete_i2chost_to_device_transfer(uint8_t* data, size_t length); +void create_shell_i2c(EventDispatcher* evtd); +} + namespace i2cdev { bool I2cDev_PPmod::init(uint8_t addr_) { if (addr_ != I2CDEV_PPMOD_ADDR_1) return false; addr = addr_; model = I2CDECMDL_PPMOD; - query_interval = 10; + query_interval = 1; // self timer will handle the update interval per subdevice + mask = get_features_mask(); + if (mask & (uint64_t)SupportedFeatures::FEAT_SHELL) { + create_shell_i2c(I2CDevManager::get_event_dispatcher()); + } return true; } void I2cDev_PPmod::update() { - auto mask = get_features_mask(); - if (mask & (uint64_t)SupportedFeatures::FEAT_GPS) { + // mask = get_features_mask(); //saved on init. replug device if something changed. //needs to revise when modules come out. + if (mask & (uint64_t)SupportedFeatures::FEAT_GPS && self_timer % SENSORUPDATETIME == 0) { auto data = get_gps_data(); if (data.has_value()) { GPSPosDataMessage msg{data.value().latitude, data.value().longitude, (int32_t)data.value().altitude, (int32_t)data.value().speed, data.value().sats_in_use}; EventDispatcher::send_message(msg); } } - if (mask & (uint64_t)SupportedFeatures::FEAT_ORIENTATION) { + if (mask & (uint64_t)SupportedFeatures::FEAT_ORIENTATION && self_timer % SENSORUPDATETIME == 0) { auto data = get_orientation_data(); if (data.has_value()) { OrientationDataMessage msg{(uint16_t)data.value().angle, (int16_t)data.value().tilt}; EventDispatcher::send_message(msg); } } - if (mask & (uint64_t)SupportedFeatures::FEAT_ENVIRONMENT) { + if (mask & (uint64_t)SupportedFeatures::FEAT_ENVIRONMENT && self_timer % SENSORUPDATETIME == 0) { auto data = get_environment_data(); if (data.has_value()) { EnvironmentDataMessage msg{data.value().temperature, data.value().humidity, data.value().pressure}; EventDispatcher::send_message(msg); } } - if (mask & (uint64_t)SupportedFeatures::FEAT_LIGHT) { + if (mask & (uint64_t)SupportedFeatures::FEAT_LIGHT && self_timer % SENSORUPDATETIME == 0) { auto data = get_light_data(); if (data.has_value()) { LightDataMessage msg{data.value()}; EventDispatcher::send_message(msg); } } + if (mask & (uint64_t)SupportedFeatures::FEAT_SHELL) { + auto commcnt = get_shell_buffer_bytes(); + if (commcnt > 0) { + bool has_more = false; + uint8_t buff[65]; // 0 th byte is the has_more flag, and size sent + do { + if (get_shell_get_buffer_data(buff, 65)) { + if (buff[0] == 0xff) { + break; // error + } + has_more = buff[0] & 0x80; + size_t size = buff[0] & 0x7F; + complete_i2chost_to_device_transfer(&buff[1], size); + } else { + has_more = false; + } + } while (has_more); + } + } + self_timer++; + if (self_timer >= 250) { + self_timer = 0; // rounding bc of uint8_t overflow + } +} + +bool I2cDev_PPmod::get_shell_get_buffer_data(uint8_t* buff, size_t len) { + Command cmd = Command::COMMAND_SHELL_MODTOPP_DATA; + return i2c_read((uint8_t*)&cmd, 2, buff, len); } std::optional I2cDev_PPmod::get_orientation_data() { @@ -106,18 +145,28 @@ std::optional I2cDev_PPmod::get_light_data() { return data; } +uint16_t I2cDev_PPmod::get_shell_buffer_bytes() { + Command cmd = Command::COMMAND_SHELL_MODTOPP_DATA_SIZE; + uint16_t data; + bool success = i2c_read((uint8_t*)&cmd, 2, (uint8_t*)&data, sizeof(uint16_t)); + if (success == false) { + return 0; + } + return data; +} + uint64_t I2cDev_PPmod::get_features_mask() { - uint64_t mask = 0; + uint64_t mask_ = 0; Command cmd = Command::COMMAND_GETFEATURE_MASK; - bool success = i2c_read((uint8_t*)&cmd, 2, (uint8_t*)&mask, sizeof(mask)); + bool success = i2c_read((uint8_t*)&cmd, 2, (uint8_t*)&mask_, sizeof(mask_)); if (success == false) { return 0; } // sanity check - if (mask == UINT64_MAX) { + if (mask_ == UINT64_MAX) { return 0; } - return mask; + return mask_; } std::optional I2cDev_PPmod::readDeviceInfo() { diff --git a/firmware/common/i2cdev_ppmod.hpp b/firmware/common/i2cdev_ppmod.hpp index 4341ff6f..7a3330cc 100644 --- a/firmware/common/i2cdev_ppmod.hpp +++ b/firmware/common/i2cdev_ppmod.hpp @@ -55,6 +55,10 @@ class I2cDev_PPmod : public I2cDev { COMMAND_GETFEAT_DATA_ORIENTATION, COMMAND_GETFEAT_DATA_ENVIRONMENT, COMMAND_GETFEAT_DATA_LIGHT, + // Shell specific communication + COMMAND_SHELL_PPTOMOD_DATA, // pp shell to esp. size not defined + COMMAND_SHELL_MODTOPP_DATA_SIZE, // how many bytes the esp has to send to pp's shell + COMMAND_SHELL_MODTOPP_DATA, // the actual bytes sent by esp. 1st byte's 1st bit is the "hasmore" flag, the remaining 7 bits are the size of the data. exactly 64 byte follows. }; @@ -85,6 +89,12 @@ class I2cDev_PPmod : public I2cDev { std::optional get_orientation_data(); std::optional get_environment_data(); std::optional get_light_data(); + uint16_t get_shell_buffer_bytes(); + bool get_shell_get_buffer_data(uint8_t* buff, size_t len); + + private: + uint8_t self_timer = 0; + uint64_t mask = 0; // feauture mask, that indicates what the device can do. this will determinate what we will query from the device }; } /* namespace i2cdev */ diff --git a/firmware/common/i2cdev_ppmod_helper.hpp b/firmware/common/i2cdev_ppmod_helper.hpp index 6a2dc079..72ccfe86 100644 --- a/firmware/common/i2cdev_ppmod_helper.hpp +++ b/firmware/common/i2cdev_ppmod_helper.hpp @@ -4,14 +4,15 @@ #include enum class SupportedFeatures : uint64_t { - FEAT_NONE = 0, - FEAT_EXT_APP = 1 << 0, - FEAT_UART = 1 << 1, - FEAT_GPS = 1 << 2, - FEAT_ORIENTATION = 1 << 3, - FEAT_ENVIRONMENT = 1 << 4, - FEAT_LIGHT = 1 << 5, - FEAT_DISPLAY = 1 << 6 + FEAT_NONE = 0, // no polling needed + FEAT_EXT_APP = 1 << 0, // supplies external app + FEAT_UART = 1 << 1, // supplies UART communication + FEAT_GPS = 1 << 2, // can be queried for GPS info + FEAT_ORIENTATION = 1 << 3, // can be queried for orientation info + FEAT_ENVIRONMENT = 1 << 4, // can be queried for environment info + FEAT_LIGHT = 1 << 5, // can be queried for light info + FEAT_DISPLAY = 1 << 6, // can handle special display output + FEAT_SHELL = 1 << 7, // can handle shell commands (polling needed for that) }; typedef struct diff --git a/firmware/common/i2cdevmanager.cpp b/firmware/common/i2cdevmanager.cpp index c04a9c98..a28ced39 100644 --- a/firmware/common/i2cdevmanager.cpp +++ b/firmware/common/i2cdevmanager.cpp @@ -41,6 +41,7 @@ bool I2CDevManager::force_scan = false; Thread* I2CDevManager::thread; std::vector I2CDevManager::devlist; Mutex I2CDevManager::mutex_list{}; +EventDispatcher* I2CDevManager::_eventDispatcher; /* DEAR DEVELOPERS! @@ -343,3 +344,11 @@ msg_t I2CDevManager::timer_fn(void* arg) { } }; // namespace i2cdev + +extern "C" int oNofityI2cFromShell(uint8_t* buff, size_t len) { + i2cdev::I2cDev* dev = i2cdev::I2CDevManager::get_dev_by_model(I2C_DEVMDL::I2CDECMDL_PPMOD); + if (!dev) return 0; // nothing to send to, so /dev/null + uint16_t reg = 9; // COMMAND_SHELL_PPTOMOD_DATA; + if (dev->i2c_write((uint8_t*)®, 2, buff, len)) return 0; + return 0; // shoud have an error handler +} diff --git a/firmware/common/i2cdevmanager.hpp b/firmware/common/i2cdevmanager.hpp index 68a72a99..4c87bcc3 100644 --- a/firmware/common/i2cdevmanager.hpp +++ b/firmware/common/i2cdevmanager.hpp @@ -87,6 +87,8 @@ class I2CDevManager { static I2cDev* get_dev_by_model(I2C_DEVMDL model); // caller function needs to cast to the specific device! static std::vector get_dev_list_by_model(); // returns the currently discovered static std::vector get_gev_list_by_addr(); // returns the currently discovered + static void setEventDispatcher(EventDispatcher* ed) { _eventDispatcher = ed; } + static EventDispatcher* get_event_dispatcher() { return _eventDispatcher; } private: static uint16_t scan_interval; @@ -99,6 +101,8 @@ class I2CDevManager { static msg_t timer_fn(void* arg); static Thread* thread; static Mutex mutex_list; + static EventDispatcher* _eventDispatcher; }; }; // namespace i2cdev + #endif \ No newline at end of file diff --git a/firmware/common/i2cdevmanager_c_api.h b/firmware/common/i2cdevmanager_c_api.h new file mode 100644 index 00000000..226656c5 --- /dev/null +++ b/firmware/common/i2cdevmanager_c_api.h @@ -0,0 +1,13 @@ +#ifndef I2CDEVMANAGER_C_API_H +#define I2CDEVMANAGER_C_API_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif +int oNofityI2cFromShell(uint8_t* buff, size_t len); +#ifdef __cplusplus +} +#endif +#endif \ No newline at end of file