diff --git a/.vscode/launch.json b/.vscode/launch.json index 3d9aa78921..d72c64a3db 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -59,6 +59,5 @@ "target/nrf52.cfg" ], } - ] } diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a839e08086..db5a163b26 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -465,6 +465,7 @@ list(APPEND SOURCE_FILES components/motion/MotionController.cpp components/ble/NimbleController.cpp components/ble/DeviceInformationService.cpp + components/ble/BleNus.cpp components/ble/CurrentTimeClient.cpp components/ble/AlertNotificationClient.cpp components/ble/DfuService.cpp @@ -506,7 +507,7 @@ list(APPEND SOURCE_FILES components/heartrate/Biquad.cpp components/heartrate/Ptagc.cpp components/heartrate/HeartRateController.cpp - + components/console/Console.cpp touchhandler/TouchHandler.cpp ) @@ -535,6 +536,7 @@ list(APPEND RECOVERY_SOURCE_FILES components/motion/MotionController.cpp components/ble/NimbleController.cpp components/ble/DeviceInformationService.cpp + components/ble/BleNus.cpp components/ble/CurrentTimeClient.cpp components/ble/AlertNotificationClient.cpp components/ble/DfuService.cpp @@ -567,6 +569,7 @@ list(APPEND RECOVERY_SOURCE_FILES components/heartrate/Ptagc.cpp components/motor/MotorController.cpp components/fs/FS.cpp + components/console/Console.cpp touchhandler/TouchHandler.cpp ) diff --git a/src/components/ble/BleNus.cpp b/src/components/ble/BleNus.cpp new file mode 100644 index 0000000000..e091a526df --- /dev/null +++ b/src/components/ble/BleNus.cpp @@ -0,0 +1,87 @@ +#include "BleNus.h" +#include "components/console/Console.h" + +using namespace Pinetime::Controllers; + +constexpr ble_uuid128_t BleNus::nusServiceUuid; +constexpr ble_uuid128_t BleNus::rxCharacteristicUuid; +constexpr ble_uuid128_t BleNus::txCharacteristicUuid; +uint16_t BleNus::attributeReadHandle; + +int BleNusCallback(uint16_t connectionHandle, uint16_t attr_handle, struct ble_gatt_access_ctxt* ctxt, void* arg) { + auto *deviceInformationService = static_cast(arg); + return deviceInformationService->OnDeviceInfoRequested(connectionHandle, attr_handle, ctxt); +} + +void BleNus::Init() { + int res = 0; + res = ble_gatts_count_cfg(serviceDefinition); + ASSERT(res == 0); + + res = ble_gatts_add_svcs(serviceDefinition); + ASSERT(res == 0); +} + +void BleNus::SetConnectionHandle(uint16_t connection_handle) { + connectionHandle = connection_handle; +} + +void BleNus::Print(const std::string str) { + os_mbuf* om; + om = ble_hs_mbuf_from_flat(str.c_str(), str.length()); + + if (om) { + ble_gattc_notify_custom(connectionHandle, attributeReadHandle, om); + } +} + +void BleNus::RegisterRxCallback(std::function f) { + this->rxDataFunction = f; +} + +int BleNus::OnDeviceInfoRequested(uint16_t connectionHandle, uint16_t attr_handle, struct ble_gatt_access_ctxt* ctxt) { + (void) connectionHandle; + (void) attr_handle; + + os_mbuf* om = ctxt->om; + + switch (ctxt->op) { + case BLE_GATT_ACCESS_OP_WRITE_CHR: + while (om) { + + // Test BLE console it with Bluefruit, NRF Toolbox (you must add enter before hitting send! + // https://devzone.nordicsemi.com/f/nordic-q-a/33687/nrf-toolbox-2-6-0-uart-does-not-send-lf-cr-or-cr-lf-as-eol) on the phone, or in + // any Chromium-based web browser https://terminal.hardwario.com/ + + rxDataFunction((char*) om->om_data, (int) om->om_len); + + om = SLIST_NEXT(om, om_next); + } + return 0; + default: + assert(0); + return BLE_ATT_ERR_UNLIKELY; + } +} + +BleNus::BleNus() + : characteristicDefinition {{ + .uuid = &rxCharacteristicUuid.u, + .access_cb = BleNusCallback, + .arg = this, + .flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_WRITE_NO_RSP, + }, + {.uuid = &txCharacteristicUuid.u, + .access_cb = BleNusCallback, + .arg = this, + .flags = BLE_GATT_CHR_F_NOTIFY, + .val_handle = &attributeReadHandle}, + {0}}, + serviceDefinition { + {/* Device Information Service */ + .type = BLE_GATT_SVC_TYPE_PRIMARY, + .uuid = &nusServiceUuid.u, + .characteristics = characteristicDefinition}, + {0}, + } { +} diff --git a/src/components/ble/BleNus.h b/src/components/ble/BleNus.h new file mode 100644 index 0000000000..e18c0acb47 --- /dev/null +++ b/src/components/ble/BleNus.h @@ -0,0 +1,49 @@ +#pragma once +#define min // workaround: nimble's min/max macros conflict with libstdc++ +#define max +#include +#undef max +#undef min +#include "Version.h" +#include + +namespace Pinetime { + namespace Components { + class Console; + } + namespace Controllers { + class BleNus { + public: + BleNus(); + void Init(); + void SetConnectionHandle(uint16_t connection_handle); + void Print(const std::string str); + + void RegisterRxCallback(std::function f); + + int OnDeviceInfoRequested(uint16_t connectionHandle, uint16_t attr_handle, struct ble_gatt_access_ctxt* ctxt); + + private: + static uint16_t attributeReadHandle; + uint16_t connectionHandle; + + std::function rxDataFunction; + + // 6E400001-B5A3-F393-E0A9-E50E24DCCA9E + static constexpr ble_uuid128_t nusServiceUuid { + .u {.type = BLE_UUID_TYPE_128}, + .value = {0x9e, 0xca, 0xdc, 0x24, 0x0e, 0xe5, 0xa9, 0xe0, 0x93, 0xf3, 0xa3, 0xb5, 0x01, 0x00, 0x40, 0x6e}}; + // 6E400002-B5A3-F393-E0A9-E50E24DCCA9E + static constexpr ble_uuid128_t rxCharacteristicUuid { + .u {.type = BLE_UUID_TYPE_128}, + .value = {0x9e, 0xca, 0xdc, 0x24, 0x0e, 0xe5, 0xa9, 0xe0, 0x93, 0xf3, 0xa3, 0xb5, 0x02, 0x00, 0x40, 0x6e}}; + // 6E400003-B5A3-F393-E0A9-E50E24DCCA9E + static constexpr ble_uuid128_t txCharacteristicUuid { + .u {.type = BLE_UUID_TYPE_128}, + .value = {0x9e, 0xca, 0xdc, 0x24, 0x0e, 0xe5, 0xa9, 0xe0, 0x93, 0xf3, 0xa3, 0xb5, 0x03, 0x00, 0x40, 0x6e}}; + + struct ble_gatt_chr_def characteristicDefinition[3]; + struct ble_gatt_svc_def serviceDefinition[2]; + }; + } +} \ No newline at end of file diff --git a/src/components/ble/NimbleController.cpp b/src/components/ble/NimbleController.cpp index 1bcae1bcf4..3e14c98ce5 100644 --- a/src/components/ble/NimbleController.cpp +++ b/src/components/ble/NimbleController.cpp @@ -14,6 +14,7 @@ #include "components/ble/NotificationManager.h" #include "components/datetime/DateTimeController.h" #include "systemtask/SystemTask.h" +#include "components/ble/BleNus.h" using namespace Pinetime::Controllers; @@ -74,6 +75,7 @@ void NimbleController::Init() { ble_svc_gatt_init(); deviceInformationService.Init(); + bleNusService.Init(); currentTimeClient.Init(); currentTimeService.Init(); musicService.Init(); @@ -181,7 +183,9 @@ int NimbleController::OnGAPEvent(ble_gap_event* event) { connectionHandle = event->connect.conn_handle; bleController.Connect(); systemTask.PushMessage(Pinetime::System::Messages::BleConnected); - // Service discovery is deferred via systemtask + connectionHandle = event->connect.conn_handle; + bleNusService.SetConnectionHandle(event->connect.conn_handle); + // Service discovery is deffered via systemtask } break; @@ -292,3 +296,4 @@ void NimbleController::NotifyBatteryLevel(uint8_t level) { batteryInformationService.NotifyBatteryLevel(connectionHandle, level); } } + diff --git a/src/components/ble/NimbleController.h b/src/components/ble/NimbleController.h index 76f89ba818..e6a2cdafcd 100644 --- a/src/components/ble/NimbleController.h +++ b/src/components/ble/NimbleController.h @@ -13,6 +13,7 @@ #include "CurrentTimeClient.h" #include "CurrentTimeService.h" #include "DeviceInformationService.h" +#include "BleNus.h" #include "DfuService.h" #include "ImmediateAlertService.h" #include "MusicService.h" @@ -71,6 +72,10 @@ namespace Pinetime { return anService; }; + Pinetime::Controllers::BleNus& bleNus() { + return bleNusService; + }; + uint16_t connHandle(); void NotifyBatteryLevel(uint8_t level); @@ -88,6 +93,7 @@ namespace Pinetime { Pinetime::Controllers::DfuService dfuService; DeviceInformationService deviceInformationService; + BleNus bleNusService; CurrentTimeClient currentTimeClient; AlertNotificationService anService; AlertNotificationClient alertNotificationClient; diff --git a/src/components/console/Console.cpp b/src/components/console/Console.cpp new file mode 100644 index 0000000000..10b297c547 --- /dev/null +++ b/src/components/console/Console.cpp @@ -0,0 +1,101 @@ +#include "Console.h" +#include "components/ble/BleNus.h" +#include "components/ble/NimbleController.h" +#include "systemtask/SystemTask.h" + +using namespace Pinetime::Components; + +Console::Console(Pinetime::System::SystemTask& systemTask, + Pinetime::Controllers::NimbleController& nimbleController, + Pinetime::Controllers::FS& fs, + Pinetime::Components::LittleVgl& lvgl, + Pinetime::Controllers::MotorController& motorController, + Pinetime::Drivers::Cst816S& touchPanel, + Pinetime::Drivers::SpiNorFlash& spiNorFlash, + Pinetime::Drivers::TwiMaster& twiMaster, + Pinetime::Controllers::MotionController& motionController) + : systemTask {systemTask}, + nimbleController {nimbleController}, + fs {fs}, + lvgl {lvgl}, + motorController {motorController}, + touchPanel {touchPanel}, + spiNorFlash {spiNorFlash}, + twiMaster {twiMaster}, + motionController {motionController} { +} + +void Console::Init() { + auto rxCallback = [this](char* str, int length) { + this->Received(str, length); + }; + + nimbleController.bleNus().RegisterRxCallback(rxCallback); +} + +void Console::Print(const std::string str) { + nimbleController.bleNus().Print(str); +} + +static bool cmdCmp(char* buffer, const std::string search) { + return strncmp(buffer, search.c_str(), search.length()) == 0; +} + +void Console::Process() { + static constexpr int maxArgumentsCount = 4; + static constexpr int maxBufferLength = 64; + + char arg_buffer[maxBufferLength]; + const char* args[maxArgumentsCount]; + + // Copy string, becase we replace ' ' with '\0' for proper string termination + strncpy(arg_buffer, rxBuffer, sizeof(arg_buffer)); + + // First argument is always command name itself + uint16_t argc = 1; + args[0] = arg_buffer; + + uint16_t param_len = strlen(rxBuffer); + + for (uint16_t i = 0; i < param_len; i++) { + if (rxBuffer[i] == ' ' && param_len > (i + 1)) { + arg_buffer[i] = '\0'; + args[argc++] = &arg_buffer[i + 1]; + } + + if (argc == maxArgumentsCount) { + // Max argument count reached + break; + } + } + + (void) args; + (void) argc; + + // Simple stupid command comparison, later would be nice to add commands lookup table with argument parsing + if (cmdCmp(rxBuffer, "VIBRATE")) { + motorController.RunForDuration(100); + } else if (cmdCmp(rxBuffer, "WKUP")) { + systemTask.PushMessage(Pinetime::System::Messages::GoToRunning); + } else if (cmdCmp(rxBuffer, "SLEEP")) { + systemTask.PushMessage(Pinetime::System::Messages::GoToSleep); + } +} + +void Console::Received(char* str, int length) { + for (int i = 0; i < length; i++) { + // Wrap if input is too long without CR/LN + if (rxPos == bufferSize - 1) { + rxPos = 0; + } + + rxBuffer[rxPos++] = str[i]; + rxBuffer[rxPos] = '\0'; // terminate for debug print + + if (str[i] == '\n' || str[i] == '\r') { + rxPos = 0; + systemTask.PushMessage(System::Messages::ConsoleProcess); + break; + } + } +} \ No newline at end of file diff --git a/src/components/console/Console.h b/src/components/console/Console.h new file mode 100644 index 0000000000..138f497cc3 --- /dev/null +++ b/src/components/console/Console.h @@ -0,0 +1,65 @@ +#pragma once + +#include +#include +#include + +namespace Pinetime { + + namespace System { + class SystemTask; + } + + namespace Controllers { + class NimbleController; + class FS; + class MotorController; + class MotionController; + } + + namespace Drivers { + class Cst816S; + class SpiNorFlash; + class TwiMaster; + } + + namespace Components { + class LittleVgl; + + class Console { + public: + Console(Pinetime::System::SystemTask& systemTask, + Pinetime::Controllers::NimbleController& nimbleController, + Pinetime::Controllers::FS& fs, + Pinetime::Components::LittleVgl& lvgl, + Pinetime::Controllers::MotorController& motorController, + Pinetime::Drivers::Cst816S& touchPanel, + Pinetime::Drivers::SpiNorFlash& spiNorFlash, + Pinetime::Drivers::TwiMaster& twiMaster, + Pinetime::Controllers::MotionController& motionController); + + void Init(); + void Process(); + void Print(std::string str); + void Received(char* str, int length); + + private: + Pinetime::System::SystemTask& systemTask; + Pinetime::Controllers::NimbleController& nimbleController; + Pinetime::Controllers::FS& fs; + Pinetime::Components::LittleVgl& lvgl; + Pinetime::Controllers::MotorController& motorController; + Pinetime::Drivers::Cst816S& touchPanel; + Pinetime::Drivers::SpiNorFlash& spiNorFlash; + Pinetime::Drivers::TwiMaster& twiMaster; + Pinetime::Controllers::MotionController& motionController; + + static constexpr int bufferSize = 32; + char rxBuffer[bufferSize]; + uint16_t rxPos; + + void CommandLvgl(const char* args[], uint16_t argc); + void AccelerometerDebug(); + }; + } +} diff --git a/src/systemtask/Messages.h b/src/systemtask/Messages.h index 5aa218d25c..732444160b 100644 --- a/src/systemtask/Messages.h +++ b/src/systemtask/Messages.h @@ -21,6 +21,7 @@ namespace Pinetime { DisableSleeping, OnNewDay, OnChargingEvent, + ConsoleProcess, SetOffAlarm, StopRinging, MeasureBatteryTimerExpired, diff --git a/src/systemtask/SystemTask.cpp b/src/systemtask/SystemTask.cpp index e0a5907a02..8fca33ef55 100644 --- a/src/systemtask/SystemTask.cpp +++ b/src/systemtask/SystemTask.cpp @@ -28,6 +28,13 @@ #include +#include +#include +#include +#include +#include + + using namespace Pinetime::System; namespace { @@ -99,10 +106,11 @@ SystemTask::SystemTask(Drivers::SpiMaster& spi, motionController {motionController}, displayApp {displayApp}, heartRateApp(heartRateApp), - fs {fs}, + fs{fs}, touchHandler {touchHandler}, - nimbleController(*this, bleController, dateTimeController, notificationManager, - batteryController, spiNorFlash, heartRateController, motionController) { + nimbleController(*this, bleController, dateTimeController, notificationManager, batteryController, spiNorFlash, heartRateController, motionController), + console(*this, nimbleController, fs, lvgl, motorController, touchPanel, spiNorFlash, twiMaster, motionController) { + } void SystemTask::Start() { @@ -163,6 +171,8 @@ void SystemTask::Work() { heartRateSensor.Disable(); heartRateApp.Start(); + console.Init(); + // Button nrf_gpio_cfg_output(15); nrf_gpio_pin_set(15); @@ -367,6 +377,10 @@ void SystemTask::Work() { nimbleController.NotifyBatteryLevel(batteryController.PercentRemaining()); break; + case Messages::ConsoleProcess: + console.Process(); + break; + default: break; } diff --git a/src/systemtask/SystemTask.h b/src/systemtask/SystemTask.h index 879c1be8a7..3b67073275 100644 --- a/src/systemtask/SystemTask.h +++ b/src/systemtask/SystemTask.h @@ -19,6 +19,7 @@ #include "components/timer/TimerController.h" #include "components/alarm/AlarmController.h" #include "components/fs/FS.h" +#include "components/console/Console.h" #include "touchhandler/TouchHandler.h" #ifdef PINETIME_IS_RECOVERY @@ -124,6 +125,7 @@ namespace Pinetime { Pinetime::Controllers::FS& fs; Pinetime::Controllers::TouchHandler& touchHandler; Pinetime::Controllers::NimbleController nimbleController; + Pinetime::Components::Console console; static void Process(void* instance); void Work();