/ firmware / src / networking / ble.cpp
ble.cpp
  1  #include "ble.h"
  2  #include <config.h>
  3  #include "../console/remote.h"
  4  #include <services/system.h>
  5  #include <manager.h>
  6  
  7  #include <Arduino.h>
  8  #include <BLEDevice.h>
  9  #include <BLEServer.h>
 10  #include <BLEUtils.h>
 11  #include <BLESecurity.h>
 12  
 13  #define NUS_SERVICE_UUID   "6E400001-B5A3-F393-E0A9-E50E24DCCA9E"
 14  #define NUS_RX_UUID        "6E400002-B5A3-F393-E0A9-E50E24DCCA9E"
 15  #define NUS_TX_UUID        "6E400003-B5A3-F393-E0A9-E50E24DCCA9E"
 16  
 17  #define SENSOR_SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
 18  #define SENSOR_STATUS_UUID  "beb5483e-36e1-4688-b7f5-ea07361b26a8"
 19  #define SENSOR_VOLTAGE_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a9"
 20  
 21  //------------------------------------------
 22  //  BLE transport
 23  //------------------------------------------
 24  static BLEServer *ble_server = nullptr;
 25  static BLECharacteristic *nus_tx = nullptr;
 26  static BLECharacteristic *nus_rx = nullptr;
 27  static BLECharacteristic *sensor_status = nullptr;
 28  static BLECharacteristic *sensor_voltage = nullptr;
 29  
 30  static char ring_buf[config::ble::RING_SIZE];
 31  static char wbuf[config::ble::WRITE_BUF];
 32  static char line[config::shell::BUF_IN];
 33  
 34  static void ble_flush(const char *data, size_t len, void *ctx) {
 35    (void)ctx;
 36    if (nus_tx && networking::ble::clientCount() > 0) {
 37      nus_tx->setValue((uint8_t *)data, len);
 38      nus_tx->notify();
 39    }
 40  }
 41  
 42  static console::remote::Shell shell(
 43    ring_buf, config::ble::RING_SIZE,
 44    wbuf, config::ble::WRITE_BUF,
 45    line, config::shell::BUF_IN,
 46    ble_flush, nullptr
 47  );
 48  
 49  //------------------------------------------
 50  //  BLE callbacks
 51  //------------------------------------------
 52  class BleServerCallbacks : public BLEServerCallbacks {
 53    void onConnect(BLEServer *server) override {
 54      uint32_t connected_clients = server->getConnectedCount();
 55      Serial.printf("[ble] client connected (%u total)\n", connected_clients);
 56  
 57      if (connected_clients == 1) {
 58        shell.reset();
 59        shell.send_prompt();
 60      }
 61  
 62      if (connected_clients < config::ble::MAX_CLIENTS)
 63        BLEDevice::startAdvertising();
 64      else
 65        BLEDevice::stopAdvertising();
 66    }
 67  
 68    void onDisconnect(BLEServer *server) override {
 69      shell.save_history();
 70      uint32_t connected_clients = server->getConnectedCount();
 71      Serial.printf("[ble] client disconnected (%d remaining)\n", connected_clients);
 72    }
 73  };
 74  
 75  class BleRxCallbacks : public BLECharacteristicCallbacks {
 76    void onWrite(BLECharacteristic *characteristic) override {
 77      String value = characteristic->getValue();
 78      shell.push_input(value.c_str(), value.length());
 79    }
 80  };
 81  
 82  //------------------------------------------
 83  //  Public API
 84  //------------------------------------------
 85  void networking::ble::initialize(void) {
 86    BLEDevice::init(config::HOSTNAME);
 87    BLEDevice::setMTU(517);
 88  
 89    // Intentionally leaked — BLE stack owns these for device lifetime.
 90    BLESecurity *pSecurity = new BLESecurity();
 91    pSecurity->setPassKey(true, config::ble::PASSKEY);
 92    pSecurity->setCapability(ESP_IO_CAP_OUT);
 93    pSecurity->setAuthenticationMode(true, true, true);
 94  
 95    ble_server = BLEDevice::createServer();
 96    ble_server->setCallbacks(new BleServerCallbacks());  // BLE stack owns
 97    ble_server->advertiseOnDisconnect(true);
 98  
 99    BLEService *nus = ble_server->createService(NUS_SERVICE_UUID);
100  
101    nus_tx = nus->createCharacteristic(
102      NUS_TX_UUID,
103      BLECharacteristic::PROPERTY_NOTIFY
104    );
105  
106    nus_rx = nus->createCharacteristic(
107      NUS_RX_UUID,
108      BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_WRITE_NR
109      | BLECharacteristic::PROPERTY_WRITE_AUTHEN
110    );
111    nus_rx->setAccessPermissions(ESP_GATT_PERM_WRITE_ENC_MITM);
112    nus_rx->setCallbacks(new BleRxCallbacks());  // BLE stack owns
113  
114    nus->start();
115  
116    BLEService *sensors = ble_server->createService(SENSOR_SERVICE_UUID);
117  
118    sensor_status = sensors->createCharacteristic(
119      SENSOR_STATUS_UUID,
120      BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY
121      | BLECharacteristic::PROPERTY_READ_AUTHEN
122    );
123    sensor_status->setAccessPermissions(ESP_GATT_PERM_READ_ENC_MITM);
124  
125    sensor_voltage = sensors->createCharacteristic(
126      SENSOR_VOLTAGE_UUID,
127      BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY
128      | BLECharacteristic::PROPERTY_READ_AUTHEN
129    );
130    sensor_voltage->setAccessPermissions(ESP_GATT_PERM_READ_ENC_MITM);
131  
132    sensors->start();
133  
134    BLEAdvertising *advertising = BLEDevice::getAdvertising();
135    advertising->addServiceUUID(NUS_SERVICE_UUID);
136    advertising->addServiceUUID(SENSOR_SERVICE_UUID);
137    advertising->setScanResponse(true);
138    advertising->setMinPreferred(0x06);
139    BLEDevice::startAdvertising();
140  
141    Serial.printf("[ble] advertising as %s (passkey: %u)\n",
142                  config::HOSTNAME, config::ble::PASSKEY);
143  }
144  
145  static uint32_t last_sensor_notify = 0;
146  
147  void networking::ble::service(void) {
148    if (networking::ble::clientCount() == 0) return;
149  
150    shell.service();
151  
152    if (millis() - last_sensor_notify > 5000) {
153      last_sensor_notify = millis();
154  
155      SystemQuery query = {
156        .preferred_storage = StorageKind::LittleFS,
157        .snapshot = {},
158      };
159      services::system::accessSnapshot(&query);
160      char buf[64];
161      snprintf(buf, sizeof(buf), "{\"heap\":%u,\"uptime\":%lu}",
162               query.snapshot.heap_free, query.snapshot.uptime_seconds);
163      sensor_status->setValue((uint8_t *)buf, strlen(buf));
164      sensor_status->notify();
165  
166      VoltageSensorData sensor_data = {};
167      if (sensors::manager::accessVoltage(&sensor_data)) {
168        snprintf(buf, sizeof(buf), "[%.4f,%.4f,%.4f,%.4f]",
169                 sensor_data.channel_volts[0], sensor_data.channel_volts[1],
170                 sensor_data.channel_volts[2], sensor_data.channel_volts[3]);
171        sensor_voltage->setValue((uint8_t *)buf, strlen(buf));
172        sensor_voltage->notify();
173      }
174    }
175  }
176  
177  bool networking::ble::isConnected(void) {
178    return networking::ble::clientCount() > 0;
179  }
180  
181  int networking::ble::clientCount(void) {
182    return ble_server ? (int)ble_server->getConnectedCount() : 0;
183  }
184  
185  #ifdef PIO_UNIT_TESTING
186  
187  #include <testing/utils.h>
188  
189  void networking::ble::test(void) {
190  }
191  
192  #endif