From 1f92b09d08cde52cf61f115a6335dfce12ad9ffd Mon Sep 17 00:00:00 2001 From: phaseloop Date: Wed, 17 Dec 2025 17:22:18 +0000 Subject: [PATCH] code format --- src/platform/nrf52/NRF52Bluetooth.cpp | 733 +++++++++++++------------- src/platform/nrf52/main-nrf52.cpp | 656 ++++++++++++----------- 2 files changed, 716 insertions(+), 673 deletions(-) diff --git a/src/platform/nrf52/NRF52Bluetooth.cpp b/src/platform/nrf52/NRF52Bluetooth.cpp index 541e3b303..e6766ca5f 100644 --- a/src/platform/nrf52/NRF52Bluetooth.cpp +++ b/src/platform/nrf52/NRF52Bluetooth.cpp @@ -10,21 +10,24 @@ #include static BLEService meshBleService = BLEService(BLEUuid(MESH_SERVICE_UUID_16)); static BLECharacteristic fromNum = BLECharacteristic(BLEUuid(FROMNUM_UUID_16)); -static BLECharacteristic fromRadio = BLECharacteristic(BLEUuid(FROMRADIO_UUID_16)); +static BLECharacteristic fromRadio = + BLECharacteristic(BLEUuid(FROMRADIO_UUID_16)); static BLECharacteristic toRadio = BLECharacteristic(BLEUuid(TORADIO_UUID_16)); -static BLECharacteristic logRadio = BLECharacteristic(BLEUuid(LOGRADIO_UUID_16)); +static BLECharacteristic logRadio = + BLECharacteristic(BLEUuid(LOGRADIO_UUID_16)); static BLEDis bledis; // DIS (Device Information Service) helper class instance static BLEBas blebas; // BAS (Battery Service) helper class instance #ifndef BLE_DFU_SECURE static BLEDfu bledfu; // DFU software update helper service #else -static BLEDfuSecure bledfusecure; // DFU software update helper service +static BLEDfuSecure bledfusecure; // DFU software update helper service #endif -// This scratch buffer is used for various bluetooth reads/writes - but it is safe because only one bt operation can be in -// process at once -// static uint8_t trBytes[_max(_max(_max(_max(ToRadio_size, RadioConfig_size), User_size), MyNodeInfo_size), FromRadio_size)]; +// This scratch buffer is used for various bluetooth reads/writes - but it is +// safe because only one bt operation can be in process at once static uint8_t +// trBytes[_max(_max(_max(_max(ToRadio_size, RadioConfig_size), User_size), +// MyNodeInfo_size), FromRadio_size)]; static uint8_t fromRadioBytes[meshtastic_FromRadio_size]; static uint8_t toRadioBytes[meshtastic_ToRadio_size]; @@ -33,427 +36,451 @@ static uint8_t lastToRadio[MAX_TO_FROM_RADIO_SIZE]; static uint16_t connectionHandle; -class BluetoothPhoneAPI : public PhoneAPI -{ - /** - * Subclasses can use this as a hook to provide custom notifications for their transport (i.e. bluetooth notifies) - */ - virtual void onNowHasData(uint32_t fromRadioNum) override - { - PhoneAPI::onNowHasData(fromRadioNum); +class BluetoothPhoneAPI : public PhoneAPI { + /** + * Subclasses can use this as a hook to provide custom notifications for their + * transport (i.e. bluetooth notifies) + */ + virtual void onNowHasData(uint32_t fromRadioNum) override { + PhoneAPI::onNowHasData(fromRadioNum); - LOG_INFO("BLE notify fromNum"); - fromNum.notify32(fromRadioNum); - } + LOG_INFO("BLE notify fromNum"); + fromNum.notify32(fromRadioNum); + } - /// Check the current underlying physical link to see if the client is currently connected - virtual bool checkIsConnected() override { return Bluefruit.connected(connectionHandle); } + /// Check the current underlying physical link to see if the client is + /// currently connected + virtual bool checkIsConnected() override { + return Bluefruit.connected(connectionHandle); + } - public: - BluetoothPhoneAPI() { api_type = TYPE_BLE; } +public: + BluetoothPhoneAPI() { api_type = TYPE_BLE; } }; static BluetoothPhoneAPI *bluetoothPhoneAPI; -void onConnect(uint16_t conn_handle) -{ - // Get the reference to current connection - BLEConnection *connection = Bluefruit.Connection(conn_handle); - connectionHandle = conn_handle; - char central_name[32] = {0}; - connection->getPeerName(central_name, sizeof(central_name)); - LOG_INFO("BLE Connected to %s", central_name); +void onConnect(uint16_t conn_handle) { + // Get the reference to current connection + BLEConnection *connection = Bluefruit.Connection(conn_handle); + connectionHandle = conn_handle; + char central_name[32] = {0}; + connection->getPeerName(central_name, sizeof(central_name)); + LOG_INFO("BLE Connected to %s", central_name); - // Notify UI (or any other interested firmware components) - meshtastic::BluetoothStatus newStatus(meshtastic::BluetoothStatus::ConnectionState::CONNECTED); - bluetoothStatus->updateStatus(&newStatus); + // Notify UI (or any other interested firmware components) + meshtastic::BluetoothStatus newStatus( + meshtastic::BluetoothStatus::ConnectionState::CONNECTED); + bluetoothStatus->updateStatus(&newStatus); } /** * Callback invoked when a connection is dropped * @param conn_handle connection where this event happens * @param reason is a BLE_HCI_STATUS_CODE which can be found in ble_hci.h */ -void onDisconnect(uint16_t conn_handle, uint8_t reason) -{ - LOG_INFO("BLE Disconnected, reason = 0x%x", reason); - if (bluetoothPhoneAPI) { - bluetoothPhoneAPI->close(); - } +void onDisconnect(uint16_t conn_handle, uint8_t reason) { + LOG_INFO("BLE Disconnected, reason = 0x%x", reason); + if (bluetoothPhoneAPI) { + bluetoothPhoneAPI->close(); + } - // Clear the last ToRadio packet buffer to avoid rejecting first packet from new connection - memset(lastToRadio, 0, sizeof(lastToRadio)); + // Clear the last ToRadio packet buffer to avoid rejecting first packet from + // new connection + memset(lastToRadio, 0, sizeof(lastToRadio)); - // Notify UI (or any other interested firmware components) - meshtastic::BluetoothStatus newStatus(meshtastic::BluetoothStatus::ConnectionState::DISCONNECTED); - bluetoothStatus->updateStatus(&newStatus); + // Notify UI (or any other interested firmware components) + meshtastic::BluetoothStatus newStatus( + meshtastic::BluetoothStatus::ConnectionState::DISCONNECTED); + bluetoothStatus->updateStatus(&newStatus); } -void onCccd(uint16_t conn_hdl, BLECharacteristic *chr, uint16_t cccd_value) -{ - // Display the raw request packet - LOG_INFO("CCCD Updated: %u", cccd_value); - // Check the characteristic this CCCD update is associated with in case - // this handler is used for multiple CCCD records. +void onCccd(uint16_t conn_hdl, BLECharacteristic *chr, uint16_t cccd_value) { + // Display the raw request packet + LOG_INFO("CCCD Updated: %u", cccd_value); + // Check the characteristic this CCCD update is associated with in case + // this handler is used for multiple CCCD records. - // According to the GATT spec: cccd value = 0x0001 means notifications are enabled - // and cccd value = 0x0002 means indications are enabled + // According to the GATT spec: cccd value = 0x0001 means notifications are + // enabled and cccd value = 0x0002 means indications are enabled - if (chr->uuid == fromNum.uuid || chr->uuid == logRadio.uuid) { - auto result = cccd_value == 2 ? chr->indicateEnabled(conn_hdl) : chr->notifyEnabled(conn_hdl); - if (result) { - LOG_INFO("Notify/Indicate enabled"); - } else { - LOG_INFO("Notify/Indicate disabled"); - } + if (chr->uuid == fromNum.uuid || chr->uuid == logRadio.uuid) { + auto result = cccd_value == 2 ? chr->indicateEnabled(conn_hdl) + : chr->notifyEnabled(conn_hdl); + if (result) { + LOG_INFO("Notify/Indicate enabled"); + } else { + LOG_INFO("Notify/Indicate disabled"); } + } } -void startAdv(void) -{ - // Advertising packet - Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE); - // IncludeService UUID - // Bluefruit.ScanResponse.addService(meshBleService); - Bluefruit.ScanResponse.addTxPower(); - Bluefruit.ScanResponse.addName(); - // Include Name - // Bluefruit.Advertising.addName(); - Bluefruit.Advertising.addService(meshBleService); - /* Start Advertising - * - Enable auto advertising if disconnected - * - Interval: fast mode = 20 ms, slow mode = 417,5 ms - * - Timeout for fast mode is 30 seconds - * - Start(timeout) with timeout = 0 will advertise forever (until connected) - * - * For recommended advertising interval - * https://developer.apple.com/library/content/qa/qa1931/_index.html - */ - Bluefruit.Advertising.restartOnDisconnect(true); - Bluefruit.Advertising.setInterval(32, 668); // in unit of 0.625 ms - Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode - Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds. FIXME, we should stop advertising after X +void startAdv(void) { + // Advertising packet + Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE); + // IncludeService UUID + // Bluefruit.ScanResponse.addService(meshBleService); + Bluefruit.ScanResponse.addTxPower(); + Bluefruit.ScanResponse.addName(); + // Include Name + // Bluefruit.Advertising.addName(); + Bluefruit.Advertising.addService(meshBleService); + /* Start Advertising + * - Enable auto advertising if disconnected + * - Interval: fast mode = 20 ms, slow mode = 417,5 ms + * - Timeout for fast mode is 30 seconds + * - Start(timeout) with timeout = 0 will advertise forever (until connected) + * + * For recommended advertising interval + * https://developer.apple.com/library/content/qa/qa1931/_index.html + */ + Bluefruit.Advertising.restartOnDisconnect(true); + Bluefruit.Advertising.setInterval(32, 668); // in unit of 0.625 ms + Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode + Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds. + // FIXME, we should stop advertising after X } // Just ack that the caller is allowed to read -static void authorizeRead(uint16_t conn_hdl) -{ - ble_gatts_rw_authorize_reply_params_t reply = {.type = BLE_GATTS_AUTHORIZE_TYPE_READ}; - reply.params.write.gatt_status = BLE_GATT_STATUS_SUCCESS; - sd_ble_gatts_rw_authorize_reply(conn_hdl, &reply); +static void authorizeRead(uint16_t conn_hdl) { + ble_gatts_rw_authorize_reply_params_t reply = { + .type = BLE_GATTS_AUTHORIZE_TYPE_READ}; + reply.params.write.gatt_status = BLE_GATT_STATUS_SUCCESS; + sd_ble_gatts_rw_authorize_reply(conn_hdl, &reply); } /** * client is starting read, pull the bytes from our API class */ -void onFromRadioAuthorize(uint16_t conn_hdl, BLECharacteristic *chr, ble_gatts_evt_read_t *request) -{ - if (request->offset == 0) { - // If the read is long, we will get multiple authorize invocations - we only populate data on the first - size_t numBytes = bluetoothPhoneAPI->getFromRadio(fromRadioBytes); - // Someone is going to read our value as soon as this callback returns. So fill it with the next message in the queue - // or make empty if the queue is empty - fromRadio.write(fromRadioBytes, numBytes); - } else { - // LOG_INFO("Ignore successor read"); - } - authorizeRead(conn_hdl); +void onFromRadioAuthorize(uint16_t conn_hdl, BLECharacteristic *chr, + ble_gatts_evt_read_t *request) { + if (request->offset == 0) { + // If the read is long, we will get multiple authorize invocations - we only + // populate data on the first + size_t numBytes = bluetoothPhoneAPI->getFromRadio(fromRadioBytes); + // Someone is going to read our value as soon as this callback returns. So + // fill it with the next message in the queue or make empty if the queue is + // empty + fromRadio.write(fromRadioBytes, numBytes); + } else { + // LOG_INFO("Ignore successor read"); + } + authorizeRead(conn_hdl); } -void onToRadioWrite(uint16_t conn_hdl, BLECharacteristic *chr, uint8_t *data, uint16_t len) -{ - LOG_INFO("toRadioWriteCb data %p, len %u", data, len); - if (memcmp(lastToRadio, data, len) != 0) { - LOG_DEBUG("New ToRadio packet"); - memcpy(lastToRadio, data, len); - bluetoothPhoneAPI->handleToRadio(data, len); - } else { - LOG_DEBUG("Drop dup ToRadio packet we just saw"); - } +void onToRadioWrite(uint16_t conn_hdl, BLECharacteristic *chr, uint8_t *data, + uint16_t len) { + LOG_INFO("toRadioWriteCb data %p, len %u", data, len); + if (memcmp(lastToRadio, data, len) != 0) { + LOG_DEBUG("New ToRadio packet"); + memcpy(lastToRadio, data, len); + bluetoothPhoneAPI->handleToRadio(data, len); + } else { + LOG_DEBUG("Drop dup ToRadio packet we just saw"); + } } -void setupMeshService(void) -{ - bluetoothPhoneAPI = new BluetoothPhoneAPI(); - meshBleService.begin(); - // Note: You must call .begin() on the BLEService before calling .begin() on - // any characteristic(s) within that service definition.. Calling .begin() on - // a BLECharacteristic will cause it to be added to the last BLEService that - // was 'begin()'ed! - auto secMode = - config.bluetooth.mode == meshtastic_Config_BluetoothConfig_PairingMode_NO_PIN ? SECMODE_OPEN : SECMODE_ENC_NO_MITM; - fromNum.setProperties(CHR_PROPS_NOTIFY | CHR_PROPS_READ); - fromNum.setPermission(secMode, SECMODE_NO_ACCESS); // FIXME, secure this!!! - fromNum.setFixedLen( - 0); // Variable len (either 0 or 4) FIXME consider changing protocol so it is fixed 4 byte len, where 0 means empty - fromNum.setMaxLen(4); - fromNum.setCccdWriteCallback(onCccd); // Optionally capture CCCD updates - // We don't yet need to hook the fromNum auth callback - // fromNum.setReadAuthorizeCallback(fromNumAuthorizeCb); - fromNum.write32(0); // Provide default fromNum of 0 - fromNum.begin(); +void setupMeshService(void) { + bluetoothPhoneAPI = new BluetoothPhoneAPI(); + meshBleService.begin(); + // Note: You must call .begin() on the BLEService before calling .begin() on + // any characteristic(s) within that service definition.. Calling .begin() on + // a BLECharacteristic will cause it to be added to the last BLEService that + // was 'begin()'ed! + auto secMode = config.bluetooth.mode == + meshtastic_Config_BluetoothConfig_PairingMode_NO_PIN + ? SECMODE_OPEN + : SECMODE_ENC_NO_MITM; + fromNum.setProperties(CHR_PROPS_NOTIFY | CHR_PROPS_READ); + fromNum.setPermission(secMode, SECMODE_NO_ACCESS); // FIXME, secure this!!! + fromNum.setFixedLen( + 0); // Variable len (either 0 or 4) FIXME consider changing protocol so + // it is fixed 4 byte len, where 0 means empty + fromNum.setMaxLen(4); + fromNum.setCccdWriteCallback(onCccd); // Optionally capture CCCD updates + // We don't yet need to hook the fromNum auth callback + // fromNum.setReadAuthorizeCallback(fromNumAuthorizeCb); + fromNum.write32(0); // Provide default fromNum of 0 + fromNum.begin(); - fromRadio.setProperties(CHR_PROPS_READ); - fromRadio.setPermission(secMode, SECMODE_NO_ACCESS); - fromRadio.setMaxLen(sizeof(fromRadioBytes)); - fromRadio.setReadAuthorizeCallback( - onFromRadioAuthorize, - false); // We don't call this callback via the adafruit queue, because we can safely run in the BLE context - fromRadio.setBuffer(fromRadioBytes, sizeof(fromRadioBytes)); // we preallocate our fromradio buffer so we won't waste space - // for two copies - fromRadio.begin(); + fromRadio.setProperties(CHR_PROPS_READ); + fromRadio.setPermission(secMode, SECMODE_NO_ACCESS); + fromRadio.setMaxLen(sizeof(fromRadioBytes)); + fromRadio.setReadAuthorizeCallback( + onFromRadioAuthorize, + false); // We don't call this callback via the adafruit queue, because we + // can safely run in the BLE context + fromRadio.setBuffer(fromRadioBytes, + sizeof(fromRadioBytes)); // we preallocate our fromradio + // buffer so we won't waste space + // for two copies + fromRadio.begin(); - toRadio.setProperties(CHR_PROPS_WRITE); - toRadio.setPermission(secMode, secMode); // FIXME secure this! - toRadio.setFixedLen(0); - toRadio.setMaxLen(512); - toRadio.setBuffer(toRadioBytes, sizeof(toRadioBytes)); - // We don't call this callback via the adafruit queue, because we can safely run in the BLE context - toRadio.setWriteCallback(onToRadioWrite, false); - toRadio.begin(); + toRadio.setProperties(CHR_PROPS_WRITE); + toRadio.setPermission(secMode, secMode); // FIXME secure this! + toRadio.setFixedLen(0); + toRadio.setMaxLen(512); + toRadio.setBuffer(toRadioBytes, sizeof(toRadioBytes)); + // We don't call this callback via the adafruit queue, because we can safely + // run in the BLE context + toRadio.setWriteCallback(onToRadioWrite, false); + toRadio.begin(); - logRadio.setProperties(CHR_PROPS_INDICATE | CHR_PROPS_NOTIFY | CHR_PROPS_READ); - logRadio.setPermission(secMode, SECMODE_NO_ACCESS); - logRadio.setMaxLen(512); - logRadio.setCccdWriteCallback(onCccd); - logRadio.write32(0); - logRadio.begin(); + logRadio.setProperties(CHR_PROPS_INDICATE | CHR_PROPS_NOTIFY | + CHR_PROPS_READ); + logRadio.setPermission(secMode, SECMODE_NO_ACCESS); + logRadio.setMaxLen(512); + logRadio.setCccdWriteCallback(onCccd); + logRadio.write32(0); + logRadio.begin(); } static uint32_t configuredPasskey; -void NRF52Bluetooth::shutdown() -{ - // Shutdown bluetooth for minimum power draw - LOG_INFO("Disable NRF52 bluetooth"); - Bluefruit.Security.setPairPasskeyCallback(NRF52Bluetooth::onUnwantedPairing); // Actively refuse (during factory reset) - disconnect(); - Bluefruit.Advertising.stop(); +void NRF52Bluetooth::shutdown() { + // Shutdown bluetooth for minimum power draw + LOG_INFO("Disable NRF52 bluetooth"); + Bluefruit.Security.setPairPasskeyCallback( + NRF52Bluetooth::onUnwantedPairing); // Actively refuse (during factory + // reset) + disconnect(); + Bluefruit.Advertising.stop(); } -void NRF52Bluetooth::startDisabled() -{ - // Setup Bluetooth - nrf52Bluetooth->setup(); - // Shutdown bluetooth for minimum power draw - Bluefruit.Advertising.stop(); - Bluefruit.setTxPower(-40); // Minimum power - LOG_INFO("Disable NRF52 Bluetooth. (Workaround: tx power min, advertise stopped)"); +void NRF52Bluetooth::startDisabled() { + // Setup Bluetooth + nrf52Bluetooth->setup(); + // Shutdown bluetooth for minimum power draw + Bluefruit.Advertising.stop(); + Bluefruit.setTxPower(-40); // Minimum power + LOG_INFO( + "Disable NRF52 Bluetooth. (Workaround: tx power min, advertise stopped)"); } -bool NRF52Bluetooth::isConnected() -{ - return Bluefruit.connected(connectionHandle); +bool NRF52Bluetooth::isConnected() { + return Bluefruit.connected(connectionHandle); } -int NRF52Bluetooth::getRssi() -{ - return 0; // FIXME figure out where to source this +int NRF52Bluetooth::getRssi() { + return 0; // FIXME figure out where to source this } -void NRF52Bluetooth::setup() -{ - // Initialise the Bluefruit module - LOG_INFO("Init the Bluefruit nRF52 module"); - Bluefruit.autoConnLed(false); - Bluefruit.configPrphBandwidth(BANDWIDTH_MAX); - Bluefruit.begin(); - // Clear existing data. - Bluefruit.Advertising.stop(); - Bluefruit.Advertising.clearData(); - Bluefruit.ScanResponse.clearData(); - if (config.bluetooth.mode != meshtastic_Config_BluetoothConfig_PairingMode_NO_PIN) { - configuredPasskey = config.bluetooth.mode == meshtastic_Config_BluetoothConfig_PairingMode_FIXED_PIN - ? config.bluetooth.fixed_pin - : random(100000, 999999); - auto pinString = std::to_string(configuredPasskey); - LOG_INFO("Bluetooth pin set to '%i'", configuredPasskey); - Bluefruit.Security.setPIN(pinString.c_str()); - Bluefruit.Security.setIOCaps(true, false, false); - Bluefruit.Security.setPairPasskeyCallback(NRF52Bluetooth::onPairingPasskey); - Bluefruit.Security.setPairCompleteCallback(NRF52Bluetooth::onPairingCompleted); - Bluefruit.Security.setSecuredCallback(NRF52Bluetooth::onConnectionSecured); - meshBleService.setPermission(SECMODE_ENC_WITH_MITM, SECMODE_ENC_WITH_MITM); - } else { - Bluefruit.Security.setIOCaps(false, false, false); - meshBleService.setPermission(SECMODE_OPEN, SECMODE_OPEN); - } - // Set the advertised device name (keep it short!) - Bluefruit.setName(getDeviceName()); - // Set the connect/disconnect callback handlers - Bluefruit.Periph.setConnectCallback(onConnect); - Bluefruit.Periph.setDisconnectCallback(onDisconnect); +void NRF52Bluetooth::setup() { + // Initialise the Bluefruit module + LOG_INFO("Init the Bluefruit nRF52 module"); + Bluefruit.autoConnLed(false); + Bluefruit.configPrphBandwidth(BANDWIDTH_MAX); + Bluefruit.begin(); + // Clear existing data. + Bluefruit.Advertising.stop(); + Bluefruit.Advertising.clearData(); + Bluefruit.ScanResponse.clearData(); + if (config.bluetooth.mode != + meshtastic_Config_BluetoothConfig_PairingMode_NO_PIN) { + configuredPasskey = + config.bluetooth.mode == + meshtastic_Config_BluetoothConfig_PairingMode_FIXED_PIN + ? config.bluetooth.fixed_pin + : random(100000, 999999); + auto pinString = std::to_string(configuredPasskey); + LOG_INFO("Bluetooth pin set to '%i'", configuredPasskey); + Bluefruit.Security.setPIN(pinString.c_str()); + Bluefruit.Security.setIOCaps(true, false, false); + Bluefruit.Security.setPairPasskeyCallback(NRF52Bluetooth::onPairingPasskey); + Bluefruit.Security.setPairCompleteCallback( + NRF52Bluetooth::onPairingCompleted); + Bluefruit.Security.setSecuredCallback(NRF52Bluetooth::onConnectionSecured); + meshBleService.setPermission(SECMODE_ENC_WITH_MITM, SECMODE_ENC_WITH_MITM); + } else { + Bluefruit.Security.setIOCaps(false, false, false); + meshBleService.setPermission(SECMODE_OPEN, SECMODE_OPEN); + } + // Set the advertised device name (keep it short!) + Bluefruit.setName(getDeviceName()); + // Set the connect/disconnect callback handlers + Bluefruit.Periph.setConnectCallback(onConnect); + Bluefruit.Periph.setDisconnectCallback(onDisconnect); - - // Do not change Slave Latency to value other than 0 !!! - // There is probably a bug in SoftDevice + certain Apple iOS versions being - // brain damaged causing connectivity problems. + // Do not change Slave Latency to value other than 0 !!! + // There is probably a bug in SoftDevice + certain Apple iOS versions being + // brain damaged causing connectivity problems. - // On one side it seems SoftDevice is using SlaveLatency value even - // if connection parameter negotation failed and phone sees it as connectivity errors. + // On one side it seems SoftDevice is using SlaveLatency value even + // if connection parameter negotation failed and phone sees it as connectivity + // errors. - // On the other hand Apple can randomly refuse any parameter negotiation and shutdown connection - // even if you meet Apple Developer Guidelines for BLE devices. Because f* you, that's why. + // On the other hand Apple can randomly refuse any parameter negotiation and + // shutdown connection even if you meet Apple Developer Guidelines for BLE + // devices. Because f* you, that's why. - // While this API call sets preferred connection parameters (PPCP) - many phones ignore it (yeah) and it seems SoftDevice - // will try to renegotiate connection parameters based on those values after phone connection. - // So those are relatively safe values so Apple braindead firmware won't get angry and at least we may try - // to negotiate some longer connection interval to save battery. + // While this API call sets preferred connection parameters (PPCP) - many + // phones ignore it (yeah) and it seems SoftDevice will try to renegotiate + // connection parameters based on those values after phone connection. So + // those are relatively safe values so Apple braindead firmware won't get + // angry and at least we may try to negotiate some longer connection interval + // to save battery. - // See https://github.com/meshtastic/firmware/pull/8858 for measurements. We are dealing with microamp savings anyway so not worth - // dying on a hill here. + // See https://github.com/meshtastic/firmware/pull/8858 for measurements. We + // are dealing with microamp savings anyway so not worth dying on a hill here. - Bluefruit.Periph.setConnSlaveLatency(0); - // 1.25 ms units - so min, max is 15, 100 ms range. - Bluefruit.Periph.setConnInterval(12, 80); + Bluefruit.Periph.setConnSlaveLatency(0); + // 1.25 ms units - so min, max is 15, 100 ms range. + Bluefruit.Periph.setConnInterval(12, 80); #ifndef BLE_DFU_SECURE - bledfu.setPermission(SECMODE_ENC_WITH_MITM, SECMODE_ENC_WITH_MITM); - bledfu.begin(); // Install the DFU helper + bledfu.setPermission(SECMODE_ENC_WITH_MITM, SECMODE_ENC_WITH_MITM); + bledfu.begin(); // Install the DFU helper #else - bledfusecure.setPermission(SECMODE_ENC_WITH_MITM, SECMODE_ENC_WITH_MITM); // add by WayenWeng - bledfusecure.begin(); // Install the DFU helper + bledfusecure.setPermission(SECMODE_ENC_WITH_MITM, + SECMODE_ENC_WITH_MITM); // add by WayenWeng + bledfusecure.begin(); // Install the DFU helper #endif - // Configure and Start the Device Information Service - LOG_INFO("Init the Device Information Service"); - bledis.setModel(optstr(HW_VERSION)); - bledis.setFirmwareRev(optstr(APP_VERSION)); - bledis.begin(); - // Start the BLE Battery Service and set it to 100% - LOG_INFO("Init the Battery Service"); - blebas.begin(); - blebas.write(0); // Unknown battery level for now - // Setup the Heart Rate Monitor service using - // BLEService and BLECharacteristic classes - LOG_INFO("Init the Mesh bluetooth service"); - setupMeshService(); - // Setup the advertising packet(s) - LOG_INFO("Set up the advertising payload(s)"); - startAdv(); - LOG_INFO("Advertise"); + // Configure and Start the Device Information Service + LOG_INFO("Init the Device Information Service"); + bledis.setModel(optstr(HW_VERSION)); + bledis.setFirmwareRev(optstr(APP_VERSION)); + bledis.begin(); + // Start the BLE Battery Service and set it to 100% + LOG_INFO("Init the Battery Service"); + blebas.begin(); + blebas.write(0); // Unknown battery level for now + // Setup the Heart Rate Monitor service using + // BLEService and BLECharacteristic classes + LOG_INFO("Init the Mesh bluetooth service"); + setupMeshService(); + // Setup the advertising packet(s) + LOG_INFO("Set up the advertising payload(s)"); + startAdv(); + LOG_INFO("Advertise"); } -void NRF52Bluetooth::resumeAdvertising() -{ - Bluefruit.Advertising.restartOnDisconnect(true); - Bluefruit.Advertising.setInterval(32, 668); // in unit of 0.625 ms - Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode - Bluefruit.Advertising.start(0); +void NRF52Bluetooth::resumeAdvertising() { + Bluefruit.Advertising.restartOnDisconnect(true); + Bluefruit.Advertising.setInterval(32, 668); // in unit of 0.625 ms + Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode + Bluefruit.Advertising.start(0); } /// Given a level between 0-100, update the BLE attribute -void updateBatteryLevel(uint8_t level) -{ - blebas.write(level); +void updateBatteryLevel(uint8_t level) { blebas.write(level); } +void NRF52Bluetooth::clearBonds() { + LOG_INFO("Clear bluetooth bonds!"); + bond_print_list(BLE_GAP_ROLE_PERIPH); + bond_print_list(BLE_GAP_ROLE_CENTRAL); + Bluefruit.Periph.clearBonds(); + Bluefruit.Central.clearBonds(); } -void NRF52Bluetooth::clearBonds() -{ - LOG_INFO("Clear bluetooth bonds!"); - bond_print_list(BLE_GAP_ROLE_PERIPH); - bond_print_list(BLE_GAP_ROLE_CENTRAL); - Bluefruit.Periph.clearBonds(); - Bluefruit.Central.clearBonds(); +void NRF52Bluetooth::onConnectionSecured(uint16_t conn_handle) { + LOG_INFO("BLE connection secured"); } -void NRF52Bluetooth::onConnectionSecured(uint16_t conn_handle) -{ - LOG_INFO("BLE connection secured"); -} -bool NRF52Bluetooth::onPairingPasskey(uint16_t conn_handle, uint8_t const passkey[6], bool match_request) -{ - char passkey1[4] = {passkey[0], passkey[1], passkey[2], '\0'}; - char passkey2[4] = {passkey[3], passkey[4], passkey[5], '\0'}; - LOG_INFO("BLE pair process started with passkey %s %s", passkey1, passkey2); - powerFSM.trigger(EVENT_BLUETOOTH_PAIR); +bool NRF52Bluetooth::onPairingPasskey(uint16_t conn_handle, + uint8_t const passkey[6], + bool match_request) { + char passkey1[4] = {passkey[0], passkey[1], passkey[2], '\0'}; + char passkey2[4] = {passkey[3], passkey[4], passkey[5], '\0'}; + LOG_INFO("BLE pair process started with passkey %s %s", passkey1, passkey2); + powerFSM.trigger(EVENT_BLUETOOTH_PAIR); - // Get passkey as string - // Note: possible leading zeros - std::string textkey; - for (uint8_t i = 0; i < 6; i++) - textkey += (char)passkey[i]; + // Get passkey as string + // Note: possible leading zeros + std::string textkey; + for (uint8_t i = 0; i < 6; i++) + textkey += (char)passkey[i]; - // Notify UI (or other components) of pairing event and passkey - meshtastic::BluetoothStatus newStatus(textkey); - bluetoothStatus->updateStatus(&newStatus); + // Notify UI (or other components) of pairing event and passkey + meshtastic::BluetoothStatus newStatus(textkey); + bluetoothStatus->updateStatus(&newStatus); -#if HAS_SCREEN && \ - !defined(MESHTASTIC_EXCLUDE_SCREEN) // Todo: migrate this display code back into Screen class, and observe bluetoothStatus - if (screen) { - screen->startAlert([](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -> void { - char btPIN[16] = "888888"; - snprintf(btPIN, sizeof(btPIN), "%06u", configuredPasskey); - int x_offset = display->width() / 2; - int y_offset = display->height() <= 80 ? 0 : 12; - display->setTextAlignment(TEXT_ALIGN_CENTER); - display->setFont(FONT_MEDIUM); - display->drawString(x_offset + x, y_offset + y, "Bluetooth"); +#if HAS_SCREEN && \ + !defined(MESHTASTIC_EXCLUDE_SCREEN) // Todo: migrate this display code back + // into Screen class, and observe + // bluetoothStatus + if (screen) { + screen->startAlert([](OLEDDisplay *display, OLEDDisplayUiState *state, + int16_t x, int16_t y) -> void { + char btPIN[16] = "888888"; + snprintf(btPIN, sizeof(btPIN), "%06u", configuredPasskey); + int x_offset = display->width() / 2; + int y_offset = display->height() <= 80 ? 0 : 12; + display->setTextAlignment(TEXT_ALIGN_CENTER); + display->setFont(FONT_MEDIUM); + display->drawString(x_offset + x, y_offset + y, "Bluetooth"); - display->setFont(FONT_SMALL); - y_offset = display->height() == 64 ? y_offset + FONT_HEIGHT_MEDIUM - 4 : y_offset + FONT_HEIGHT_MEDIUM + 5; - display->drawString(x_offset + x, y_offset + y, "Enter this code"); + display->setFont(FONT_SMALL); + y_offset = display->height() == 64 ? y_offset + FONT_HEIGHT_MEDIUM - 4 + : y_offset + FONT_HEIGHT_MEDIUM + 5; + display->drawString(x_offset + x, y_offset + y, "Enter this code"); - display->setFont(FONT_LARGE); - String displayPin(btPIN); - String pin = displayPin.substring(0, 3) + " " + displayPin.substring(3, 6); - y_offset = display->height() == 64 ? y_offset + FONT_HEIGHT_SMALL - 5 : y_offset + FONT_HEIGHT_SMALL + 5; - display->drawString(x_offset + x, y_offset + y, pin); + display->setFont(FONT_LARGE); + String displayPin(btPIN); + String pin = + displayPin.substring(0, 3) + " " + displayPin.substring(3, 6); + y_offset = display->height() == 64 ? y_offset + FONT_HEIGHT_SMALL - 5 + : y_offset + FONT_HEIGHT_SMALL + 5; + display->drawString(x_offset + x, y_offset + y, pin); - display->setFont(FONT_SMALL); - String deviceName = "Name: "; - deviceName.concat(getDeviceName()); - y_offset = display->height() == 64 ? y_offset + FONT_HEIGHT_LARGE - 6 : y_offset + FONT_HEIGHT_LARGE + 5; - display->drawString(x_offset + x, y_offset + y, deviceName); - }); - } + display->setFont(FONT_SMALL); + String deviceName = "Name: "; + deviceName.concat(getDeviceName()); + y_offset = display->height() == 64 ? y_offset + FONT_HEIGHT_LARGE - 6 + : y_offset + FONT_HEIGHT_LARGE + 5; + display->drawString(x_offset + x, y_offset + y, deviceName); + }); + } #endif - if (match_request) { - uint32_t start_time = millis(); - while (millis() < start_time + 30000) { - if (!Bluefruit.connected(conn_handle)) - break; - } + if (match_request) { + uint32_t start_time = millis(); + while (millis() < start_time + 30000) { + if (!Bluefruit.connected(conn_handle)) + break; } - LOG_INFO("BLE passkey pair: match_request=%i", match_request); - return true; + } + LOG_INFO("BLE passkey pair: match_request=%i", match_request); + return true; } // Actively refuse new BLE pairings -// After clearing bonds (at factory reset), clients seem initially able to attempt to re-pair, even with advertising disabled. -// On NRF52Bluetooth::shutdown, we change the pairing callback to this method, to aggressively refuse any connection attempts. -bool NRF52Bluetooth::onUnwantedPairing(uint16_t conn_handle, uint8_t const passkey[6], bool match_request) -{ - NRF52Bluetooth::disconnect(); - return false; +// After clearing bonds (at factory reset), clients seem initially able to +// attempt to re-pair, even with advertising disabled. On +// NRF52Bluetooth::shutdown, we change the pairing callback to this method, to +// aggressively refuse any connection attempts. +bool NRF52Bluetooth::onUnwantedPairing(uint16_t conn_handle, + uint8_t const passkey[6], + bool match_request) { + NRF52Bluetooth::disconnect(); + return false; } // Disconnect any BLE connections -void NRF52Bluetooth::disconnect() -{ - uint8_t connection_num = Bluefruit.connected(); - if (connection_num) { - // Close all connections. We're only expecting one. - for (uint8_t i = 0; i < connection_num; i++) - Bluefruit.disconnect(i); +void NRF52Bluetooth::disconnect() { + uint8_t connection_num = Bluefruit.connected(); + if (connection_num) { + // Close all connections. We're only expecting one. + for (uint8_t i = 0; i < connection_num; i++) + Bluefruit.disconnect(i); - // Wait for disconnection - while (Bluefruit.connected()) - yield(); + // Wait for disconnection + while (Bluefruit.connected()) + yield(); - LOG_INFO("Ended BLE connection"); - } + LOG_INFO("Ended BLE connection"); + } } -void NRF52Bluetooth::onPairingCompleted(uint16_t conn_handle, uint8_t auth_status) -{ - if (auth_status == BLE_GAP_SEC_STATUS_SUCCESS) { - LOG_INFO("BLE pair success"); - meshtastic::BluetoothStatus newConnectedStatus(meshtastic::BluetoothStatus::ConnectionState::CONNECTED); - bluetoothStatus->updateStatus(&newConnectedStatus); - } else { - LOG_INFO("BLE pair failed"); - // Notify UI (or any other interested firmware components) - meshtastic::BluetoothStatus newDisconnectedStatus(meshtastic::BluetoothStatus::ConnectionState::DISCONNECTED); - bluetoothStatus->updateStatus(&newDisconnectedStatus); - } +void NRF52Bluetooth::onPairingCompleted(uint16_t conn_handle, + uint8_t auth_status) { + if (auth_status == BLE_GAP_SEC_STATUS_SUCCESS) { + LOG_INFO("BLE pair success"); + meshtastic::BluetoothStatus newConnectedStatus( + meshtastic::BluetoothStatus::ConnectionState::CONNECTED); + bluetoothStatus->updateStatus(&newConnectedStatus); + } else { + LOG_INFO("BLE pair failed"); + // Notify UI (or any other interested firmware components) + meshtastic::BluetoothStatus newDisconnectedStatus( + meshtastic::BluetoothStatus::ConnectionState::DISCONNECTED); + bluetoothStatus->updateStatus(&newDisconnectedStatus); + } - // Todo: migrate this display code back into Screen class, and observe bluetoothStatus - if (screen) { - screen->endAlert(); - } + // Todo: migrate this display code back into Screen class, and observe + // bluetoothStatus + if (screen) { + screen->endAlert(); + } } -void NRF52Bluetooth::sendLog(const uint8_t *logMessage, size_t length) -{ - if (!isConnected() || length > 512) - return; - if (logRadio.indicateEnabled()) - logRadio.indicate(logMessage, (uint16_t)length); - else - logRadio.notify(logMessage, (uint16_t)length); +void NRF52Bluetooth::sendLog(const uint8_t *logMessage, size_t length) { + if (!isConnected() || length > 512) + return; + if (logRadio.indicateEnabled()) + logRadio.indicate(logMessage, (uint16_t)length); + else + logRadio.notify(logMessage, (uint16_t)length); } \ No newline at end of file diff --git a/src/platform/nrf52/main-nrf52.cpp b/src/platform/nrf52/main-nrf52.cpp index 169fd2109..2936d2aae 100644 --- a/src/platform/nrf52/main-nrf52.cpp +++ b/src/platform/nrf52/main-nrf52.cpp @@ -38,200 +38,202 @@ void variant_shutdown() {} static nrfx_wdt_t nrfx_wdt = NRFX_WDT_INSTANCE(0); static nrfx_wdt_channel_id nrfx_wdt_channel_id_nrf52_main; -static inline void debugger_break(void) -{ - __asm volatile("bkpt #0x01\n\t" - "mov pc, lr\n\t"); +static inline void debugger_break(void) { + __asm volatile("bkpt #0x01\n\t" + "mov pc, lr\n\t"); } -bool loopCanSleep() -{ - // turn off sleep only while connected via USB - // return true; - return !Serial; // the bool operator on the nrf52 serial class returns true if connected to a PC currently - // return !(TinyUSBDevice.mounted() && !TinyUSBDevice.suspended()); +bool loopCanSleep() { + // turn off sleep only while connected via USB + // return true; + return !Serial; // the bool operator on the nrf52 serial class returns true if + // connected to a PC currently + // return !(TinyUSBDevice.mounted() && !TinyUSBDevice.suspended()); } // handle standard gcc assert failures -void __attribute__((noreturn)) __assert_func(const char *file, int line, const char *func, const char *failedexpr) -{ - LOG_ERROR("assert failed %s: %d, %s, test=%s", file, line, func, failedexpr); - // debugger_break(); FIXME doesn't work, possibly not for segger - // Reboot cpu - NVIC_SystemReset(); +void __attribute__((noreturn)) __assert_func(const char *file, int line, + const char *func, + const char *failedexpr) { + LOG_ERROR("assert failed %s: %d, %s, test=%s", file, line, func, failedexpr); + // debugger_break(); FIXME doesn't work, possibly not for segger + // Reboot cpu + NVIC_SystemReset(); } -void getMacAddr(uint8_t *dmac) -{ - const uint8_t *src = (const uint8_t *)NRF_FICR->DEVICEADDR; - dmac[5] = src[0]; - dmac[4] = src[1]; - dmac[3] = src[2]; - dmac[2] = src[3]; - dmac[1] = src[4]; - dmac[0] = src[5] | 0xc0; // MSB high two bits get set elsewhere in the bluetooth stack +void getMacAddr(uint8_t *dmac) { + const uint8_t *src = (const uint8_t *)NRF_FICR->DEVICEADDR; + dmac[5] = src[0]; + dmac[4] = src[1]; + dmac[3] = src[2]; + dmac[2] = src[3]; + dmac[1] = src[4]; + dmac[0] = src[5] | + 0xc0; // MSB high two bits get set elsewhere in the bluetooth stack } -static void initBrownout() -{ - auto vccthresh = POWER_POFCON_THRESHOLD_V24; +static void initBrownout() { + auto vccthresh = POWER_POFCON_THRESHOLD_V24; - auto err_code = sd_power_pof_enable(POWER_POFCON_POF_Enabled); - assert(err_code == NRF_SUCCESS); + auto err_code = sd_power_pof_enable(POWER_POFCON_POF_Enabled); + assert(err_code == NRF_SUCCESS); - err_code = sd_power_pof_threshold_set(vccthresh); - assert(err_code == NRF_SUCCESS); + err_code = sd_power_pof_threshold_set(vccthresh); + assert(err_code == NRF_SUCCESS); - // We don't bother with setting up brownout if soft device is disabled - because during production we always use softdevice + // We don't bother with setting up brownout if soft device is disabled - + // because during production we always use softdevice } -// This is a public global so that the debugger can set it to false automatically from our gdbinit +// This is a public global so that the debugger can set it to false +// automatically from our gdbinit bool useSoftDevice = true; // Set to false for easier debugging #if !MESHTASTIC_EXCLUDE_BLUETOOTH -void setBluetoothEnable(bool enable) -{ - // For debugging use: don't use bluetooth - if (!useSoftDevice) { - if (enable) - LOG_INFO("Disable NRF52 BLUETOOTH WHILE DEBUGGING"); - return; - } +void setBluetoothEnable(bool enable) { + // For debugging use: don't use bluetooth + if (!useSoftDevice) { + if (enable) + LOG_INFO("Disable NRF52 BLUETOOTH WHILE DEBUGGING"); + return; + } - // If user disabled bluetooth: init then disable advertising & reduce power - // Workaround. Avoid issue where device hangs several days after boot.. - // Allegedly, no significant increase in power consumption - if (!config.bluetooth.enabled) { - static bool initialized = false; - if (!initialized) { - nrf52Bluetooth = new NRF52Bluetooth(); - nrf52Bluetooth->startDisabled(); - initBrownout(); - initialized = true; - } - return; + // If user disabled bluetooth: init then disable advertising & reduce power + // Workaround. Avoid issue where device hangs several days after boot.. + // Allegedly, no significant increase in power consumption + if (!config.bluetooth.enabled) { + static bool initialized = false; + if (!initialized) { + nrf52Bluetooth = new NRF52Bluetooth(); + nrf52Bluetooth->startDisabled(); + initBrownout(); + initialized = true; } + return; + } - if (enable) { - powerMon->setState(meshtastic_PowerMon_State_BT_On); + if (enable) { + powerMon->setState(meshtastic_PowerMon_State_BT_On); - // If not yet set-up - if (!nrf52Bluetooth) { - LOG_DEBUG("Init NRF52 Bluetooth"); - nrf52Bluetooth = new NRF52Bluetooth(); - nrf52Bluetooth->setup(); + // If not yet set-up + if (!nrf52Bluetooth) { + LOG_DEBUG("Init NRF52 Bluetooth"); + nrf52Bluetooth = new NRF52Bluetooth(); + nrf52Bluetooth->setup(); - // We delay brownout init until after BLE because BLE starts soft device - initBrownout(); - } - // Already setup, apparently - else - nrf52Bluetooth->resumeAdvertising(); - } - // Disable (if previously set-up) - else if (nrf52Bluetooth) { - powerMon->clearState(meshtastic_PowerMon_State_BT_On); - nrf52Bluetooth->shutdown(); + // We delay brownout init until after BLE because BLE starts soft device + initBrownout(); } + // Already setup, apparently + else + nrf52Bluetooth->resumeAdvertising(); + } + // Disable (if previously set-up) + else if (nrf52Bluetooth) { + powerMon->clearState(meshtastic_PowerMon_State_BT_On); + nrf52Bluetooth->shutdown(); + } } #else #warning NRF52 "Bluetooth disable" workaround does not apply to builds with MESHTASTIC_EXCLUDE_BLUETOOTH void setBluetoothEnable(bool enable) {} #endif /** - * Override printf to use the SEGGER output library (note - this does not effect the printf method on the debug console) + * Override printf to use the SEGGER output library (note - this does not effect + * the printf method on the debug console) */ -int printf(const char *fmt, ...) -{ - va_list args; - va_start(args, fmt); - auto res = SEGGER_RTT_vprintf(0, fmt, &args); - va_end(args); - return res; +int printf(const char *fmt, ...) { + va_list args; + va_start(args, fmt); + auto res = SEGGER_RTT_vprintf(0, fmt, &args); + va_end(args); + return res; } -namespace -{ +namespace { constexpr uint8_t NRF52_MAGIC_LFS_IS_CORRUPT = 0xF5; constexpr uint32_t MULTIPLE_CORRUPTION_DELAY_MILLIS = 20 * 60 * 1000; static unsigned long millis_until_formatting_again = 0; -// Report the critical error from loop(), giving a chance for the screen to be initialized first. -inline void reportLittleFSCorruptionOnce() -{ - static bool report_corruption = !!millis_until_formatting_again; - if (report_corruption) { - report_corruption = false; - RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_FLASH_CORRUPTION_UNRECOVERABLE); - } +// Report the critical error from loop(), giving a chance for the screen to be +// initialized first. +inline void reportLittleFSCorruptionOnce() { + static bool report_corruption = !!millis_until_formatting_again; + if (report_corruption) { + report_corruption = false; + RECORD_CRITICALERROR( + meshtastic_CriticalErrorCode_FLASH_CORRUPTION_UNRECOVERABLE); + } } } // namespace -void preFSBegin() -{ - // The GPREGRET register keeps its value across warm boots. Check that this is a warm boot and, if GPREGRET - // is set to NRF52_MAGIC_LFS_IS_CORRUPT, format LittleFS. - if (!(NRF_POWER->RESETREAS == 0 && NRF_POWER->GPREGRET == NRF52_MAGIC_LFS_IS_CORRUPT)) - return; - NRF_POWER->GPREGRET = 0; - millis_until_formatting_again = millis() + MULTIPLE_CORRUPTION_DELAY_MILLIS; - InternalFS.format(); - LOG_INFO("LittleFS format complete; restoring default settings"); +void preFSBegin() { + // The GPREGRET register keeps its value across warm boots. Check that this is + // a warm boot and, if GPREGRET is set to NRF52_MAGIC_LFS_IS_CORRUPT, format + // LittleFS. + if (!(NRF_POWER->RESETREAS == 0 && + NRF_POWER->GPREGRET == NRF52_MAGIC_LFS_IS_CORRUPT)) + return; + NRF_POWER->GPREGRET = 0; + millis_until_formatting_again = millis() + MULTIPLE_CORRUPTION_DELAY_MILLIS; + InternalFS.format(); + LOG_INFO("LittleFS format complete; restoring default settings"); } -extern "C" void lfs_assert(const char *reason) -{ - LOG_ERROR("LittleFS corruption detected: %s", reason); - if (millis_until_formatting_again > millis()) { - RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_FLASH_CORRUPTION_UNRECOVERABLE); - const long millis_remain = millis_until_formatting_again - millis(); - LOG_WARN("Pausing %d seconds to avoid wear on flash storage", millis_remain / 1000); - delay(millis_remain); - } - LOG_INFO("Rebooting to format LittleFS"); - delay(500); // Give the serial port a bit of time to output that last message. - // Try setting GPREGRET with the SoftDevice first. If that fails (perhaps because the SD hasn't been initialize yet) then set - // NRF_POWER->GPREGRET directly. - if (!(sd_power_gpregret_clr(0, 0xFF) == NRF_SUCCESS && sd_power_gpregret_set(0, NRF52_MAGIC_LFS_IS_CORRUPT) == NRF_SUCCESS)) { - NRF_POWER->GPREGRET = NRF52_MAGIC_LFS_IS_CORRUPT; - } - NVIC_SystemReset(); +extern "C" void lfs_assert(const char *reason) { + LOG_ERROR("LittleFS corruption detected: %s", reason); + if (millis_until_formatting_again > millis()) { + RECORD_CRITICALERROR( + meshtastic_CriticalErrorCode_FLASH_CORRUPTION_UNRECOVERABLE); + const long millis_remain = millis_until_formatting_again - millis(); + LOG_WARN("Pausing %d seconds to avoid wear on flash storage", + millis_remain / 1000); + delay(millis_remain); + } + LOG_INFO("Rebooting to format LittleFS"); + delay(500); // Give the serial port a bit of time to output that last message. + // Try setting GPREGRET with the SoftDevice first. If that fails (perhaps + // because the SD hasn't been initialize yet) then set NRF_POWER->GPREGRET + // directly. + if (!(sd_power_gpregret_clr(0, 0xFF) == NRF_SUCCESS && + sd_power_gpregret_set(0, NRF52_MAGIC_LFS_IS_CORRUPT) == NRF_SUCCESS)) { + NRF_POWER->GPREGRET = NRF52_MAGIC_LFS_IS_CORRUPT; + } + NVIC_SystemReset(); } -void checkSDEvents() -{ - if (useSoftDevice) { - uint32_t evt; - while (NRF_SUCCESS == sd_evt_get(&evt)) { - switch (evt) { - case NRF_EVT_POWER_FAILURE_WARNING: - RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_BROWNOUT); - break; +void checkSDEvents() { + if (useSoftDevice) { + uint32_t evt; + while (NRF_SUCCESS == sd_evt_get(&evt)) { + switch (evt) { + case NRF_EVT_POWER_FAILURE_WARNING: + RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_BROWNOUT); + break; - default: - LOG_DEBUG("Unexpected SDevt %d", evt); - break; - } - } - } else { - if (NRF_POWER->EVENTS_POFWARN) - RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_BROWNOUT); + default: + LOG_DEBUG("Unexpected SDevt %d", evt); + break; + } } + } else { + if (NRF_POWER->EVENTS_POFWARN) + RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_BROWNOUT); + } } -void nrf52Loop() -{ - { - static bool watchdog_running = false; - if (!watchdog_running) { - nrfx_wdt_enable(&nrfx_wdt); - watchdog_running = true; - } +void nrf52Loop() { + { + static bool watchdog_running = false; + if (!watchdog_running) { + nrfx_wdt_enable(&nrfx_wdt); + watchdog_running = true; } - nrfx_wdt_channel_feed(&nrfx_wdt, nrfx_wdt_channel_id_nrf52_main); + } + nrfx_wdt_channel_feed(&nrfx_wdt, nrfx_wdt_channel_id_nrf52_main); - checkSDEvents(); - reportLittleFSCorruptionOnce(); + checkSDEvents(); + reportLittleFSCorruptionOnce(); } #ifdef USE_SEMIHOSTING @@ -240,263 +242,277 @@ void nrf52Loop() /** * Note: this variable is in BSS and therfore false by default. But the gdbinit - * file will be installing a temporary breakpoint that changes wantSemihost to true. + * file will be installing a temporary breakpoint that changes wantSemihost to + * true. */ bool wantSemihost; /** * Turn on semihosting if the ICE debugger wants it. */ -void nrf52InitSemiHosting() -{ - if (wantSemihost) { - static SemihostingStream semiStream; - // We must dynamically alloc because the constructor does semihost operations which - // would crash any load not talking to a debugger - semiStream.open(); - semiStream.println("Semihosting starts!"); - // Redirect our serial output to instead go via the ICE port - console->setDestination(&semiStream); - } +void nrf52InitSemiHosting() { + if (wantSemihost) { + static SemihostingStream semiStream; + // We must dynamically alloc because the constructor does semihost + // operations which would crash any load not talking to a debugger + semiStream.open(); + semiStream.println("Semihosting starts!"); + // Redirect our serial output to instead go via the ICE port + console->setDestination(&semiStream); + } } #endif -void nrf52Setup() -{ +void nrf52Setup() { #ifdef ADC_V - pinMode(ADC_V, INPUT); + pinMode(ADC_V, INPUT); #endif - uint32_t why = NRF_POWER->RESETREAS; - // per - // https://infocenter.nordicsemi.com/index.jsp?topic=%2Fcom.nordic.infocenter.nrf52832.ps.v1.1%2Fpower.html - LOG_DEBUG("Reset reason: 0x%x", why); + uint32_t why = NRF_POWER->RESETREAS; + // per + // https://infocenter.nordicsemi.com/index.jsp?topic=%2Fcom.nordic.infocenter.nrf52832.ps.v1.1%2Fpower.html + LOG_DEBUG("Reset reason: 0x%x", why); #ifdef USE_SEMIHOSTING - nrf52InitSemiHosting(); + nrf52InitSemiHosting(); #endif - // Per - // https://devzone.nordicsemi.com/nordic/nordic-blog/b/blog/posts/monitor-mode-debugging-with-j-link-and-gdbeclipse - // This is the recommended setting for Monitor Mode Debugging - NVIC_SetPriority(DebugMonitor_IRQn, 6UL); + // Per + // https://devzone.nordicsemi.com/nordic/nordic-blog/b/blog/posts/monitor-mode-debugging-with-j-link-and-gdbeclipse + // This is the recommended setting for Monitor Mode Debugging + NVIC_SetPriority(DebugMonitor_IRQn, 6UL); #ifdef BQ25703A_ADDR - auto *bq = new BQ25713(); - if (!bq->setup()) - LOG_ERROR("ERROR! Charge controller init failed"); + auto *bq = new BQ25713(); + if (!bq->setup()) + LOG_ERROR("ERROR! Charge controller init failed"); #endif - // Init random seed - union seedParts { - uint32_t seed32; - uint8_t seed8[4]; - } seed; - nRFCrypto.begin(); - nRFCrypto.Random.generate(seed.seed8, sizeof(seed.seed8)); - LOG_DEBUG("Set random seed %u", seed.seed32); - randomSeed(seed.seed32); - nRFCrypto.end(); + // Init random seed + union seedParts { + uint32_t seed32; + uint8_t seed8[4]; + } seed; + nRFCrypto.begin(); + nRFCrypto.Random.generate(seed.seed8, sizeof(seed.seed8)); + LOG_DEBUG("Set random seed %u", seed.seed32); + randomSeed(seed.seed32); + nRFCrypto.end(); - // Set up nrfx watchdog. Do not enable the watchdog yet (we do that - // the first time through the main loop), so that other threads can - // allocate their own wdt channel to protect themselves from hangs. - nrfx_wdt_config_t wdt0_config = { - .behaviour = NRF_WDT_BEHAVIOUR_PAUSE_SLEEP_HALT, .reload_value = APP_WATCHDOG_SECS * 1000, - // Note: Not using wdt interrupts. - // .interrupt_priority = NRFX_WDT_DEFAULT_CONFIG_IRQ_PRIORITY - }; - nrfx_err_t r = nrfx_wdt_init(&nrfx_wdt, &wdt0_config, - nullptr // Watchdog event handler, not used, we just reset. - ); - assert(r == NRFX_SUCCESS); + // Set up nrfx watchdog. Do not enable the watchdog yet (we do that + // the first time through the main loop), so that other threads can + // allocate their own wdt channel to protect themselves from hangs. + nrfx_wdt_config_t wdt0_config = { + .behaviour = NRF_WDT_BEHAVIOUR_PAUSE_SLEEP_HALT, + .reload_value = APP_WATCHDOG_SECS * 1000, + // Note: Not using wdt interrupts. + // .interrupt_priority = NRFX_WDT_DEFAULT_CONFIG_IRQ_PRIORITY + }; + nrfx_err_t r = + nrfx_wdt_init(&nrfx_wdt, &wdt0_config, + nullptr // Watchdog event handler, not used, we just reset. + ); + assert(r == NRFX_SUCCESS); - r = nrfx_wdt_channel_alloc(&nrfx_wdt, &nrfx_wdt_channel_id_nrf52_main); - assert(r == NRFX_SUCCESS); + r = nrfx_wdt_channel_alloc(&nrfx_wdt, &nrfx_wdt_channel_id_nrf52_main); + assert(r == NRFX_SUCCESS); - - // print LFCLK debug info + // print LFCLK debug info // const char *clkSource = NULL; - // switch (NRF_CLOCK->LFCLKSRC & 0x03){ - // case CLOCK_LFCLKSRC_SRC_Xtal: - // clkSource = "XTAL"; - // break; - // case CLOCK_LFCLKSRC_SRC_RC: - /// clkSource = "RC"; - // break; - // } - - //LOG_DEBUG("LFCLK source: %s", clkSource); + // switch (NRF_CLOCK->LFCLKSRC & 0x03){ + // case CLOCK_LFCLKSRC_SRC_Xtal: + // clkSource = "XTAL"; + // break; + // case CLOCK_LFCLKSRC_SRC_RC: + /// clkSource = "RC"; + // break; + // } + // LOG_DEBUG("LFCLK source: %s", clkSource); } -void cpuDeepSleep(uint32_t msecToWake) -{ - // FIXME, configure RTC or button press to wake us - // FIXME, power down SPI, I2C, RAMs +void cpuDeepSleep(uint32_t msecToWake) { + // FIXME, configure RTC or button press to wake us + // FIXME, power down SPI, I2C, RAMs #if HAS_WIRE - Wire.end(); + Wire.end(); #endif - SPI.end(); + SPI.end(); #if SPI_INTERFACES_COUNT > 1 - SPI1.end(); + SPI1.end(); #endif - if (Serial) // Another check in case of disabled default serial, does nothing bad - Serial.end(); // This may cause crashes as debug messages continue to flow. + if (Serial) // Another check in case of disabled default serial, does nothing + // bad + Serial.end(); // This may cause crashes as debug messages continue to flow. - // This causes troubles with waking up on nrf52 (on pro-micro in particular): - // we have no Serial1 in use on nrf52, check Serial and GPS modules. + // This causes troubles with waking up on nrf52 (on pro-micro in + // particular): we have no Serial1 in use on nrf52, check Serial and GPS + // modules. #ifdef PIN_SERIAL1_RX - if (Serial1) // A straightforward solution to the wake from deepsleep problem - Serial1.end(); + if (Serial1) // A straightforward solution to the wake from deepsleep problem + Serial1.end(); #endif - setBluetoothEnable(false); + setBluetoothEnable(false); #ifdef RAK4630 #ifdef PIN_3V3_EN - digitalWrite(PIN_3V3_EN, LOW); + digitalWrite(PIN_3V3_EN, LOW); #endif #ifdef AQ_SET_PIN - // RAK-12039 set pin for Air quality sensor - digitalWrite(AQ_SET_PIN, LOW); + // RAK-12039 set pin for Air quality sensor + digitalWrite(AQ_SET_PIN, LOW); #endif #ifdef RAK14014 - // GPIO restores input status, otherwise there will be leakage current - nrf_gpio_cfg_default(TFT_BL); - nrf_gpio_cfg_default(TFT_DC); - nrf_gpio_cfg_default(TFT_CS); - nrf_gpio_cfg_default(TFT_SCLK); - nrf_gpio_cfg_default(TFT_MOSI); - nrf_gpio_cfg_default(TFT_MISO); - nrf_gpio_cfg_default(SCREEN_TOUCH_INT); - nrf_gpio_cfg_default(WB_I2C1_SCL); - nrf_gpio_cfg_default(WB_I2C1_SDA); + // GPIO restores input status, otherwise there will be leakage current + nrf_gpio_cfg_default(TFT_BL); + nrf_gpio_cfg_default(TFT_DC); + nrf_gpio_cfg_default(TFT_CS); + nrf_gpio_cfg_default(TFT_SCLK); + nrf_gpio_cfg_default(TFT_MOSI); + nrf_gpio_cfg_default(TFT_MISO); + nrf_gpio_cfg_default(SCREEN_TOUCH_INT); + nrf_gpio_cfg_default(WB_I2C1_SCL); + nrf_gpio_cfg_default(WB_I2C1_SDA); - // nrf_gpio_cfg_default(WB_I2C2_SCL); - // nrf_gpio_cfg_default(WB_I2C2_SDA); + // nrf_gpio_cfg_default(WB_I2C2_SCL); + // nrf_gpio_cfg_default(WB_I2C2_SDA); #endif #endif #ifdef MESHLINK #ifdef PIN_WD_EN - digitalWrite(PIN_WD_EN, LOW); + digitalWrite(PIN_WD_EN, LOW); #endif #endif #if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_MESH_SOLAR) - nrf_gpio_cfg_default(PIN_GPS_PPS); - detachInterrupt(PIN_GPS_PPS); - detachInterrupt(PIN_BUTTON1); + nrf_gpio_cfg_default(PIN_GPS_PPS); + detachInterrupt(PIN_GPS_PPS); + detachInterrupt(PIN_BUTTON1); #endif #ifdef ELECROW_ThinkNode_M1 - for (int pin = 0; pin < 48; pin++) { - if (pin == 17 || pin == 19 || pin == 20 || pin == 22 || pin == 23 || pin == 24 || pin == 25 || pin == 9 || pin == 10 || - pin == PIN_BUTTON1 || pin == PIN_BUTTON2) { - continue; - } - pinMode(pin, OUTPUT); + for (int pin = 0; pin < 48; pin++) { + if (pin == 17 || pin == 19 || pin == 20 || pin == 22 || pin == 23 || + pin == 24 || pin == 25 || pin == 9 || pin == 10 || pin == PIN_BUTTON1 || + pin == PIN_BUTTON2) { + continue; } - for (int pin = 0; pin < 48; pin++) { - if (pin == 17 || pin == 19 || pin == 20 || pin == 22 || pin == 23 || pin == 24 || pin == 25 || pin == 9 || pin == 10 || - pin == PIN_BUTTON1 || pin == PIN_BUTTON2) { - continue; - } - digitalWrite(pin, LOW); + pinMode(pin, OUTPUT); + } + for (int pin = 0; pin < 48; pin++) { + if (pin == 17 || pin == 19 || pin == 20 || pin == 22 || pin == 23 || + pin == 24 || pin == 25 || pin == 9 || pin == 10 || pin == PIN_BUTTON1 || + pin == PIN_BUTTON2) { + continue; } - for (int pin = 0; pin < 48; pin++) { - if (pin == 17 || pin == 19 || pin == 20 || pin == 22 || pin == 23 || pin == 24 || pin == 25 || pin == 9 || pin == 10 || - pin == PIN_BUTTON1 || pin == PIN_BUTTON2) { - continue; - } - NRF_GPIO->DIRCLR = (1 << pin); + digitalWrite(pin, LOW); + } + for (int pin = 0; pin < 48; pin++) { + if (pin == 17 || pin == 19 || pin == 20 || pin == 22 || pin == 23 || + pin == 24 || pin == 25 || pin == 9 || pin == 10 || pin == PIN_BUTTON1 || + pin == PIN_BUTTON2) { + continue; } + NRF_GPIO->DIRCLR = (1 << pin); + } #endif - variant_shutdown(); + variant_shutdown(); - // Sleepy trackers or sensors can low power "sleep" - // Don't enter this if we're sleeping portMAX_DELAY, since that's a shutdown event - if (msecToWake != portMAX_DELAY && - (IS_ONE_OF(config.device.role, meshtastic_Config_DeviceConfig_Role_TRACKER, - meshtastic_Config_DeviceConfig_Role_TAK_TRACKER, meshtastic_Config_DeviceConfig_Role_SENSOR) && - config.power.is_power_saving == true)) { - sd_power_mode_set(NRF_POWER_MODE_LOWPWR); - delay(msecToWake); - NVIC_SystemReset(); - } else { - // Resume on user button press - // https://github.com/lyusupov/SoftRF/blob/81c519ca75693b696752235d559e881f2e0511ee/software/firmware/source/SoftRF/src/platform/nRF52.cpp#L1738 - constexpr uint32_t DFU_MAGIC_SKIP = 0x6d; - sd_power_gpregret_clr(0, 0xFF); // Clear the register before setting a new values in it for stability reasons - sd_power_gpregret_set(0, DFU_MAGIC_SKIP); // Equivalent NRF_POWER->GPREGRET = DFU_MAGIC_SKIP + // Sleepy trackers or sensors can low power "sleep" + // Don't enter this if we're sleeping portMAX_DELAY, since that's a shutdown + // event + if (msecToWake != portMAX_DELAY && + (IS_ONE_OF(config.device.role, + meshtastic_Config_DeviceConfig_Role_TRACKER, + meshtastic_Config_DeviceConfig_Role_TAK_TRACKER, + meshtastic_Config_DeviceConfig_Role_SENSOR) && + config.power.is_power_saving == true)) { + sd_power_mode_set(NRF_POWER_MODE_LOWPWR); + delay(msecToWake); + NVIC_SystemReset(); + } else { + // Resume on user button press + // https://github.com/lyusupov/SoftRF/blob/81c519ca75693b696752235d559e881f2e0511ee/software/firmware/source/SoftRF/src/platform/nRF52.cpp#L1738 + constexpr uint32_t DFU_MAGIC_SKIP = 0x6d; + sd_power_gpregret_clr(0, 0xFF); // Clear the register before setting a new + // values in it for stability reasons + sd_power_gpregret_set( + 0, DFU_MAGIC_SKIP); // Equivalent NRF_POWER->GPREGRET = DFU_MAGIC_SKIP - // FIXME, use system off mode with ram retention for key state? - // FIXME, use non-init RAM per - // https://devzone.nordicsemi.com/f/nordic-q-a/48919/ram-retention-settings-with-softdevice-enabled + // FIXME, use system off mode with ram retention for key state? + // FIXME, use non-init RAM per + // https://devzone.nordicsemi.com/f/nordic-q-a/48919/ram-retention-settings-with-softdevice-enabled #ifdef ELECROW_ThinkNode_M1 - nrf_gpio_cfg_input(PIN_BUTTON1, NRF_GPIO_PIN_PULLUP); // Configure the pin to be woken up as an input - nrf_gpio_pin_sense_t sense = NRF_GPIO_PIN_SENSE_LOW; - nrf_gpio_cfg_sense_set(PIN_BUTTON1, sense); + nrf_gpio_cfg_input( + PIN_BUTTON1, + NRF_GPIO_PIN_PULLUP); // Configure the pin to be woken up as an input + nrf_gpio_pin_sense_t sense = NRF_GPIO_PIN_SENSE_LOW; + nrf_gpio_cfg_sense_set(PIN_BUTTON1, sense); - nrf_gpio_cfg_input(PIN_BUTTON2, NRF_GPIO_PIN_PULLUP); - nrf_gpio_pin_sense_t sense1 = NRF_GPIO_PIN_SENSE_LOW; - nrf_gpio_cfg_sense_set(PIN_BUTTON2, sense1); + nrf_gpio_cfg_input(PIN_BUTTON2, NRF_GPIO_PIN_PULLUP); + nrf_gpio_pin_sense_t sense1 = NRF_GPIO_PIN_SENSE_LOW; + nrf_gpio_cfg_sense_set(PIN_BUTTON2, sense1); #endif #ifdef PROMICRO_DIY_TCXO - nrf_gpio_cfg_input(BUTTON_PIN, NRF_GPIO_PIN_PULLUP); // Enable internal pull-up on the button pin - nrf_gpio_pin_sense_t sense = NRF_GPIO_PIN_SENSE_LOW; // Configure SENSE signal on low edge - nrf_gpio_cfg_sense_set(BUTTON_PIN, sense); // Apply SENSE to wake up the device from the deep sleep + nrf_gpio_cfg_input( + BUTTON_PIN, + NRF_GPIO_PIN_PULLUP); // Enable internal pull-up on the button pin + nrf_gpio_pin_sense_t sense = + NRF_GPIO_PIN_SENSE_LOW; // Configure SENSE signal on low edge + nrf_gpio_cfg_sense_set( + BUTTON_PIN, + sense); // Apply SENSE to wake up the device from the deep sleep #endif #ifdef BATTERY_LPCOMP_INPUT - // Wake up if power rises again - nrf_lpcomp_config_t c; - c.reference = BATTERY_LPCOMP_THRESHOLD; - c.detection = NRF_LPCOMP_DETECT_UP; - c.hyst = NRF_LPCOMP_HYST_NOHYST; - nrf_lpcomp_configure(NRF_LPCOMP, &c); - nrf_lpcomp_input_select(NRF_LPCOMP, BATTERY_LPCOMP_INPUT); - nrf_lpcomp_enable(NRF_LPCOMP); + // Wake up if power rises again + nrf_lpcomp_config_t c; + c.reference = BATTERY_LPCOMP_THRESHOLD; + c.detection = NRF_LPCOMP_DETECT_UP; + c.hyst = NRF_LPCOMP_HYST_NOHYST; + nrf_lpcomp_configure(NRF_LPCOMP, &c); + nrf_lpcomp_input_select(NRF_LPCOMP, BATTERY_LPCOMP_INPUT); + nrf_lpcomp_enable(NRF_LPCOMP); - battery_adcEnable(); + battery_adcEnable(); - nrf_lpcomp_task_trigger(NRF_LPCOMP, NRF_LPCOMP_TASK_START); - while (!nrf_lpcomp_event_check(NRF_LPCOMP, NRF_LPCOMP_EVENT_READY)) - ; + nrf_lpcomp_task_trigger(NRF_LPCOMP, NRF_LPCOMP_TASK_START); + while (!nrf_lpcomp_event_check(NRF_LPCOMP, NRF_LPCOMP_EVENT_READY)) + ; #endif - auto ok = sd_power_system_off(); - if (ok != NRF_SUCCESS) { - LOG_ERROR("FIXME: Ignoring soft device (EasyDMA pending?) and forcing system-off!"); - NRF_POWER->SYSTEMOFF = 1; - } + auto ok = sd_power_system_off(); + if (ok != NRF_SUCCESS) { + LOG_ERROR("FIXME: Ignoring soft device (EasyDMA pending?) and forcing " + "system-off!"); + NRF_POWER->SYSTEMOFF = 1; } + } - // The following code should not be run, because we are off - while (1) { - delay(5000); - LOG_DEBUG("."); - } + // The following code should not be run, because we are off + while (1) { + delay(5000); + LOG_DEBUG("."); + } } -void clearBonds() -{ - if (!nrf52Bluetooth) { - nrf52Bluetooth = new NRF52Bluetooth(); - nrf52Bluetooth->setup(); - } - nrf52Bluetooth->clearBonds(); +void clearBonds() { + if (!nrf52Bluetooth) { + nrf52Bluetooth = new NRF52Bluetooth(); + nrf52Bluetooth->setup(); + } + nrf52Bluetooth->clearBonds(); } -void enterDfuMode() -{ +void enterDfuMode() { // SDK kit does not have native USB like almost all other NRF52 boards #ifdef NRF_USE_SERIAL_DFU - enterSerialDfu(); + enterSerialDfu(); #else - enterUf2Dfu(); + enterUf2Dfu(); #endif }